ebird-cli (empty) → 0.1.0.0
raw patch · 5 files changed
+1724/−0 lines, 5 filesdep +aesondep +aeson-prettydep +attoparsec
Dependencies added: aeson, aeson-pretty, attoparsec, base, bytestring, directory, ebird-api, ebird-cli, ebird-client, filepath, optparse-applicative, text
Files
- CHANGELOG.md +5/−0
- LICENSE +20/−0
- Main.hs +16/−0
- ebird-cli.cabal +62/−0
- src/Data/EBird/CLI.hs +1621/−0
+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for ebird-cli++## 0.1.0.0 -- 2023-07-30++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2023 Finley McIlwaine++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ Main.hs view
@@ -0,0 +1,16 @@+module Main where++-- |+-- Module : Main+-- Copyright : (c) 2023 Finley McIlwaine+-- License : MIT (see LICENSE)+--+-- Maintainer : Finley McIlwaine <finleymcilwaine@gmail.com>+--+-- An executable command-line utility for interacting with the+-- [eBird API](https://documenter.getpostman.com/view/664302/S1ENwy59).++import Data.EBird.CLI (eBirdCli)++main :: IO ()+main = eBirdCli
+ ebird-cli.cabal view
@@ -0,0 +1,62 @@+cabal-version: 3.0+name: ebird-cli+version: 0.1.0.0+synopsis:+ A command-line utility for interacting with the+ eBird API.+description:+ A library containing the functions used to implement a command-line utility+ for interacting with the+ [eBird API](https://documenter.getpostman.com/view/664302/S1ENwy59).+license: MIT+license-file: LICENSE+author: Finley McIlwaine+maintainer: finleymcilwaine@gmail.com+copyright: 2023 Finley McIlwaine+category: Web+build-type: Simple+extra-doc-files: CHANGELOG.md+bug-reports: https://github.com/FinleyMcIlwaine/ebird-haskell/issues+homepage: https://github.com/FinleyMcIlwaine/ebird-haskell++tested-with:+ GHC == 8.10.7+ , GHC == 9.2.7+ , GHC == 9.4.5+ , GHC == 9.6.2++common common+ build-depends:+ base >= 4.13.3.0 && < 4.19+ default-extensions:+ ImportQualifiedPost+ LambdaCase+ OverloadedStrings+ RecordWildCards+ default-language: Haskell2010++executable ebird+ import: common+ main-is:+ Main.hs+ build-depends:+ ebird-cli++library+ import: common+ exposed-modules:+ Data.EBird.CLI+ build-depends:+ ebird-api >= 0.1.0.0 && < 0.2+ , ebird-client >= 0.1.0.0 && < 0.2++ , aeson >= 1.5.6.0 && < 2.2+ , aeson-pretty >= 0.8.8 && < 0.9+ , attoparsec >= 0.14.1 && < 0.15+ , bytestring >= 0.10.12.0 && < 0.12+ , directory >= 1.3.6.0 && < 1.4+ , filepath >= 1.4.2.1 && < 1.5+ , optparse-applicative >= 0.16.1.0 && < 0.19+ , text >= 1.2.4.1 && < 2.1+ hs-source-dirs:+ src
+ src/Data/EBird/CLI.hs view
@@ -0,0 +1,1621 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE DataKinds #-}++-- |+-- Module : Data.EBird.CLI+-- Copyright : (c) 2023 Finley McIlwaine+-- License : MIT (see LICENSE)+--+-- Maintainer : Finley McIlwaine <finleymcilwaine@gmail.com>+--+-- Functions used to implement a command-line utility for interacting with the+-- [eBird API](https://documenter.getpostman.com/view/664302/S1ENwy59).++module Data.EBird.CLI where++import Control.Exception+import Data.Aeson+import Data.Aeson.Encode.Pretty+import Data.Attoparsec.Text qualified as A+#if !MIN_VERSION_bytestring(0,11,0)+-- Data.ByteString.Char8 does not export 'toStrict' until 0.11.0.0+import Data.ByteString.Lazy qualified as BS (toStrict)+#endif+import Data.ByteString.Char8 qualified as BS+import Data.Char+import Data.Text (Text)+import Data.Text qualified as Text+import Data.Text.IO qualified as Text+import Options.Applicative+import System.Directory+import System.Environment+import System.Exit+import System.FilePath+import Text.Printf++import Data.EBird.API+import Data.EBird.Client++-- | Entry point for the @ebird@ CLI. Parses the command arguments, selects an+-- API key, and executes the command.+--+-- The API key may be provided as a command-line option. If the key option is+-- not provided, it is read from the file @~\/.ebird\/key.txt@. If that file is+-- unavailable for reading, and no key option is provided, the application+-- exits (if the command requires a key).+eBirdCli :: IO ()+eBirdCli = do+ (optKey, c) <- execParser opts+ fileKey <- readEBirdAPIKey+ let getKey =+ case optKey <|> fileKey of+ Just k -> pure k+ Nothing -> do+ eBirdFail+ ( "An API key is required for this command, but no API key\n" <>+ "was provided via the `-k` option and no API key file was\n" <>+ "found at ~/.ebird/key.txt. Exiting."+ )+ runEBirdCommand getKey c+ where+ opts :: ParserInfo (Maybe Text, EBirdCommand)+ opts =+ info+ (eBirdCommand <**> helper)+ ( header "ebird - Go birding on your command line!"+ <> progDesc "Query the official eBird API"+ )++-- | Read an eBird API key from @~\/.ebird\/key.txt@. If the file exists and is+-- available for reading, the result is 'Just' the contents of the file,+-- stripped of leading/trailing whitespace. Otherwise, the result is 'Nothing'.+readEBirdAPIKey :: IO (Maybe Text)+readEBirdAPIKey = do+ home <- getHomeDirectory+ catch+ ( Just . Text.strip <$>+ Text.readFile (home </> ".ebird" </> "key" <.> "txt")+ )+ ( \(_ :: IOException) -> pure Nothing+ )++-- | Run an 'EBirdCommand' with a given API key.+runEBirdCommand+ :: IO Text+ -- ^ Get an API key (this may fail if not found or provided)+ -> EBirdCommand+ -- ^ Command to execute+ -> IO ()+runEBirdCommand getAPIKey = \case+ RecentObservationsCommand RecentObservationsOptions{..} -> do+ apiKey <- getAPIKey+ res <- askEBird $+ recentObservations_ apiKey+ recentObservationsRegion+ recentObservationsBack+ recentObservationsCategories+ recentObservationsHotspots+ recentObservationsProvisionals+ recentObservationsMaxResults+ recentObservationsSubRegions+ recentObservationsSPPLocale+ handleResponse res++ RecentNotableObservationsCommand RecentNotableObservationsOptions{..} -> do+ apiKey <- getAPIKey+ res <- askEBird $+ recentNotableObservations_ apiKey+ recentNotableObservationsRegion+ recentNotableObservationsBack+ recentNotableObservationsDetail+ recentNotableObservationsHotspots+ recentNotableObservationsMaxResults+ recentNotableObservationsSubRegions+ recentNotableObservationsSPPLocale+ handleResponse res++ RecentSpeciesObservationsCommand RecentSpeciesObservationsOptions{..} -> do+ apiKey <- getAPIKey+ res <- askEBird $+ recentSpeciesObservations_ apiKey+ recentSpeciesObservationsRegion+ recentSpeciesObservationsSpecies+ recentSpeciesObservationsBack+ recentSpeciesObservationsHotspots+ recentSpeciesObservationsProvisionals+ recentSpeciesObservationsMaxResults+ recentSpeciesObservationsSubRegions+ recentSpeciesObservationsSPPLocale+ handleResponse res++ RecentNearbyObservationsCommand RecentNearbyObservationsOptions{..} -> do+ apiKey <- getAPIKey+ res <- askEBird $+ recentNearbyObservations_ apiKey+ recentNearbyObservationsLatitude+ recentNearbyObservationsLongitude+ recentNearbyObservationsDist+ recentNearbyObservationsBack+ recentNearbyObservationsCategories+ recentNearbyObservationsHotspots+ recentNearbyObservationsProvisionals+ recentNearbyObservationsMaxResults+ recentNearbyObservationsSortBy+ recentNearbyObservationsSPPLocale+ handleResponse res++ RecentNearbySpeciesObservationsCommand RecentNearbySpeciesObservationsOptions{..} -> do+ apiKey <- getAPIKey+ res <- askEBird $+ recentNearbySpeciesObservations_ apiKey+ recentNearbySpeciesObservationsSpecies+ recentNearbySpeciesObservationsLatitude+ recentNearbySpeciesObservationsLongitude+ recentNearbySpeciesObservationsDist+ recentNearbySpeciesObservationsBack+ recentNearbySpeciesObservationsCategories+ recentNearbySpeciesObservationsHotspots+ recentNearbySpeciesObservationsProvisionals+ recentNearbySpeciesObservationsMaxResults+ recentNearbySpeciesObservationsSortBy+ recentNearbySpeciesObservationsSPPLocale+ handleResponse res++ RecentNearestSpeciesObservationsCommand RecentNearestSpeciesObservationsOptions{..} -> do+ apiKey <- getAPIKey+ res <- askEBird $+ recentNearestSpeciesObservations_ apiKey+ recentNearestSpeciesObservationsSpecies+ recentNearestSpeciesObservationsLatitude+ recentNearestSpeciesObservationsLongitude+ recentNearestSpeciesObservationsDist+ recentNearestSpeciesObservationsBack+ recentNearestSpeciesObservationsHotspots+ recentNearestSpeciesObservationsProvisionals+ recentNearestSpeciesObservationsMaxResults+ recentNearestSpeciesObservationsSPPLocale+ handleResponse res++ RecentNearbyNotableObservationsCommand RecentNearbyNotableObservationsOptions{..} -> do+ apiKey <- getAPIKey+ res <- askEBird $+ recentNearbyNotableObservations_ apiKey+ recentNearbyNotableObservationsLatitude+ recentNearbyNotableObservationsLongitude+ recentNearbyNotableObservationsDist+ recentNearbyNotableObservationsDetail+ recentNearbyNotableObservationsBack+ recentNearbyNotableObservationsHotspots+ recentNearbyNotableObservationsMaxResults+ recentNearbyNotableObservationsSPPLocale+ handleResponse res++ HistoricalObservationsCommand HistoricalObservationsOptions{..} -> do+ apiKey <- getAPIKey+ let (y,m,d) = eBirdDateToGregorian historicalObservationsDate+ res <- askEBird $+ historicalObservations_ apiKey+ historicalObservationsRegion+ y m d+ historicalObservationsCategories+ historicalObservationsDetail+ historicalObservationsHotspots+ historicalObservationsProvisionals+ historicalObservationsMaxResults+ historicalObservationsRank+ historicalObservationsSubRegions+ historicalObservationsSPPLocale+ handleResponse res++ RecentChecklistsCommand RecentChecklistsOptions{..} -> do+ apiKey <- getAPIKey+ res <- askEBird $+ recentChecklists_ apiKey+ recentChecklistsRegion+ recentChecklistsMaxResults+ handleResponse res++ Top100Command Top100Options{..} -> do+ apiKey <- getAPIKey+ let (y,m,d) = eBirdDateToGregorian top100Date+ res <- askEBird $+ top100_ apiKey+ top100Region+ y m d+ top100RankedBy+ top100MaxResults+ handleResponse res++ ChecklistFeedCommand ChecklistFeedOptions{..} -> do+ apiKey <- getAPIKey+ let (y,m,d) = eBirdDateToGregorian checklistFeedDate+ res <- askEBird $+ checklistFeed_ apiKey+ checklistFeedRegion+ y m d+ checklistFeedSortBy+ checklistFeedMaxResults+ handleResponse res++ RegionalStatisticsCommand RegionalStatisticsOptions{..} -> do+ apiKey <- getAPIKey+ let (y,m,d) = eBirdDateToGregorian regionalStatisticsDate+ res <- askEBird $+ regionalStatistics_ apiKey+ regionalStatisticsRegion+ y m d+ handleResponse res++ SpeciesListCommand SpeciesListOptions{..} -> do+ apiKey <- getAPIKey+ res <- askEBird $+ speciesList_ apiKey speciesListRegion+ handleResponse res++ ViewChecklistCommand ViewChecklistOptions{..} -> do+ apiKey <- getAPIKey+ res <- askEBird $+ viewChecklist_ apiKey viewChecklistSubId+ handleResponse res++ RegionHotspotsCommand RegionHotspotsOptions{..} -> do+ res <- askEBird $+ regionHotspots_+ regionHotspotsRegion+ regionHotspotsBack+ regionHotspotsFmt+ handleResponse res++ NearbyHotspotsCommand NearbyHotspotsOptions{..} -> do+ res <- askEBird $+ nearbyHotspots_+ nearbyHotspotsLatitude+ nearbyHotspotsLongitude+ nearbyHotspotsDist+ nearbyHotspotsBack+ nearbyHotspotsFmt+ handleResponse res++ HotspotInfoCommand HotspotInfoOptions{..} -> do+ res <- askEBird $ hotspotInfo hotspotInfoLocation+ handleResponse res++ TaxonomyCommand TaxonomyOptions{..} -> do+ res <- askEBird $+ taxonomy_+ taxonomyTaxonomyCategories+ taxonomyFormat+ taxonomySPPLocale+ taxonomySpecies+ taxonomyVersion+ handleResponse res++ TaxonomicFormsCommand TaxonomicFormsOptions{..} -> do+ apiKey <- getAPIKey+ res <- askEBird $+ taxonomicForms_ apiKey+ taxonomicFormsSpecies+ handleResponse res++ TaxaLocaleCodesCommand TaxaLocaleCodesOptions{..} -> do+ apiKey <- getAPIKey+ res <- askEBird $+ taxaLocaleCodes_ apiKey+ taxaLocaleCodesAcceptLanguage+ handleResponse res++ TaxonomyVersionsCommand -> askEBird taxonomyVersions >>= handleResponse++ TaxonomicGroupsCommand TaxonomicGroupsOptions{..} -> do+ res <- askEBird $+ taxonomicGroups_+ taxonomicGroupsSPPGrouping+ taxonomicGroupsSPPLocale+ handleResponse res++ RegionInfoCommand RegionInfoOptions{..} -> do+ apiKey <- getAPIKey+ res <- askEBird $+ regionInfo_ apiKey+ regionInfoRegion+ regionInfoRegionNameFormat+ handleResponse res++ SubRegionListCommand SubRegionListOptions{..} -> do+ apiKey <- getAPIKey+ res <- askEBird $+ subRegionList apiKey+ subRegionListRegionType+ subRegionListParentRegionCode+ handleResponse res++ AdjacentRegionsCommand AdjacentRegionsOptions{..} -> do+ apiKey <- getAPIKey+ res <- askEBird $+ adjacentRegions apiKey+ adjacentRegionsRegion+ handleResponse res+ where+ handleResponse :: ToJSON a => Either ClientError a -> IO ()+ handleResponse = \case+ Right v -> printResJSON v+ Left err ->+ eBirdFail $+ "An error occurred while executing the request:\n" <>+ show err+++-- | Simply prints a value as prettified JSON+printResJSON :: ToJSON a => a -> IO ()+printResJSON = BS.putStrLn . BS.toStrict . encodePretty++-- | Print a string to stderr, prepended with a context string, and exit with+-- failure status.+eBirdFail :: String -> IO a+eBirdFail msg = do+ progName <- getProgName+ die+ ( progName <> ": Something went wrong!\n\n"+ <> unlines (map (" " <>) (lines msg))+ )++-------------------------------------------------------------------------------+-- Command types+-------------------------------------------------------------------------------++-- | Each 'EBirdCommand' corresponds to an endpoint of the eBird API+data EBirdCommand+ = RecentObservationsCommand RecentObservationsOptions+ | RecentNotableObservationsCommand RecentNotableObservationsOptions+ | RecentSpeciesObservationsCommand RecentSpeciesObservationsOptions+ | RecentNearbyObservationsCommand RecentNearbyObservationsOptions+ | RecentNearbySpeciesObservationsCommand RecentNearbySpeciesObservationsOptions+ | RecentNearestSpeciesObservationsCommand RecentNearestSpeciesObservationsOptions+ | RecentNearbyNotableObservationsCommand RecentNearbyNotableObservationsOptions+ | HistoricalObservationsCommand HistoricalObservationsOptions+ | RecentChecklistsCommand RecentChecklistsOptions+ | Top100Command Top100Options+ | ChecklistFeedCommand ChecklistFeedOptions+ | RegionalStatisticsCommand RegionalStatisticsOptions+ | SpeciesListCommand SpeciesListOptions+ | ViewChecklistCommand ViewChecklistOptions+ | RegionHotspotsCommand RegionHotspotsOptions+ | NearbyHotspotsCommand NearbyHotspotsOptions+ | HotspotInfoCommand HotspotInfoOptions+ | TaxonomyCommand TaxonomyOptions+ | TaxonomicFormsCommand TaxonomicFormsOptions+ | TaxaLocaleCodesCommand TaxaLocaleCodesOptions+ | TaxonomyVersionsCommand+ | TaxonomicGroupsCommand TaxonomicGroupsOptions+ | RegionInfoCommand RegionInfoOptions+ | SubRegionListCommand SubRegionListOptions+ | AdjacentRegionsCommand AdjacentRegionsOptions+ deriving (Show, Eq)++-- | Options for the @recent-observations@ command.+data RecentObservationsOptions =+ RecentObservationsOptions+ { recentObservationsRegion :: RegionCode+ , recentObservationsBack :: Maybe Integer+ , recentObservationsCategories :: Maybe TaxonomyCategories+ , recentObservationsHotspots :: Maybe Bool+ , recentObservationsProvisionals :: Maybe Bool+ , recentObservationsMaxResults :: Maybe Integer+ , recentObservationsSubRegions :: Maybe RegionCode+ , recentObservationsSPPLocale :: Maybe SPPLocale+ }+ deriving (Show, Read, Eq)++-- | Options for the @recent-notable-observations@ command.+data RecentNotableObservationsOptions =+ RecentNotableObservationsOptions+ { recentNotableObservationsRegion :: RegionCode+ , recentNotableObservationsBack :: Maybe Integer+ , recentNotableObservationsDetail :: Maybe DetailLevel+ , recentNotableObservationsHotspots :: Maybe Bool+ , recentNotableObservationsMaxResults :: Maybe Integer+ , recentNotableObservationsSubRegions :: Maybe RegionCode+ , recentNotableObservationsSPPLocale :: Maybe SPPLocale+ }+ deriving (Show, Read, Eq)++-- | Options for the @recent-species-observations@ command.+data RecentSpeciesObservationsOptions =+ RecentSpeciesObservationsOptions+ { recentSpeciesObservationsRegion :: RegionCode+ , recentSpeciesObservationsSpecies :: SpeciesCode+ , recentSpeciesObservationsBack :: Maybe Integer+ , recentSpeciesObservationsHotspots :: Maybe Bool+ , recentSpeciesObservationsProvisionals :: Maybe Bool+ , recentSpeciesObservationsMaxResults :: Maybe Integer+ , recentSpeciesObservationsSubRegions :: Maybe RegionCode+ , recentSpeciesObservationsSPPLocale :: Maybe SPPLocale+ }+ deriving (Show, Read, Eq)++-- | Options for the @recent-nearby-observations@ command.+data RecentNearbyObservationsOptions =+ RecentNearbyObservationsOptions+ { recentNearbyObservationsLatitude :: Double+ , recentNearbyObservationsLongitude :: Double+ , recentNearbyObservationsDist :: Maybe Integer+ , recentNearbyObservationsBack :: Maybe Integer+ , recentNearbyObservationsCategories :: Maybe TaxonomyCategories+ , recentNearbyObservationsHotspots :: Maybe Bool+ , recentNearbyObservationsProvisionals :: Maybe Bool+ , recentNearbyObservationsMaxResults :: Maybe Integer+ , recentNearbyObservationsSortBy :: Maybe SortObservationsBy+ , recentNearbyObservationsSPPLocale :: Maybe SPPLocale+ }+ deriving (Show, Read, Eq)++-- | Options for the @recent-nearby-species-observations@ command.+data RecentNearbySpeciesObservationsOptions =+ RecentNearbySpeciesObservationsOptions+ { recentNearbySpeciesObservationsSpecies :: SpeciesCode+ , recentNearbySpeciesObservationsLatitude :: Double+ , recentNearbySpeciesObservationsLongitude :: Double+ , recentNearbySpeciesObservationsDist :: Maybe Integer+ , recentNearbySpeciesObservationsBack :: Maybe Integer+ , recentNearbySpeciesObservationsCategories :: Maybe TaxonomyCategories+ , recentNearbySpeciesObservationsHotspots :: Maybe Bool+ , recentNearbySpeciesObservationsProvisionals :: Maybe Bool+ , recentNearbySpeciesObservationsMaxResults :: Maybe Integer+ , recentNearbySpeciesObservationsSortBy :: Maybe SortObservationsBy+ , recentNearbySpeciesObservationsSPPLocale :: Maybe SPPLocale+ }+ deriving (Show, Read, Eq)++-- | Options for the @recent-nearest-species-observations@ command.+data RecentNearestSpeciesObservationsOptions =+ RecentNearestSpeciesObservationsOptions+ { recentNearestSpeciesObservationsSpecies :: SpeciesCode+ , recentNearestSpeciesObservationsLatitude :: Double+ , recentNearestSpeciesObservationsLongitude :: Double+ , recentNearestSpeciesObservationsDist :: Maybe Integer+ , recentNearestSpeciesObservationsBack :: Maybe Integer+ , recentNearestSpeciesObservationsHotspots :: Maybe Bool+ , recentNearestSpeciesObservationsProvisionals :: Maybe Bool+ , recentNearestSpeciesObservationsMaxResults :: Maybe Integer+ , recentNearestSpeciesObservationsSPPLocale :: Maybe SPPLocale+ }+ deriving (Show, Read, Eq)++-- | Options for the @recent-nearby-notable-observations@ command.+data RecentNearbyNotableObservationsOptions =+ RecentNearbyNotableObservationsOptions+ { recentNearbyNotableObservationsLatitude :: Double+ , recentNearbyNotableObservationsLongitude :: Double+ , recentNearbyNotableObservationsDist :: Maybe Integer+ , recentNearbyNotableObservationsDetail :: Maybe DetailLevel+ , recentNearbyNotableObservationsBack :: Maybe Integer+ , recentNearbyNotableObservationsHotspots :: Maybe Bool+ , recentNearbyNotableObservationsMaxResults :: Maybe Integer+ , recentNearbyNotableObservationsSPPLocale :: Maybe SPPLocale+ }+ deriving (Show, Read, Eq)++-- | Options for the @historical-observations@ command.+data HistoricalObservationsOptions =+ HistoricalObservationsOptions+ { historicalObservationsRegion :: RegionCode+ , historicalObservationsDate :: EBirdDate+ , historicalObservationsCategories :: Maybe TaxonomyCategories+ , historicalObservationsDetail :: Maybe DetailLevel+ , historicalObservationsHotspots :: Maybe Bool+ , historicalObservationsProvisionals :: Maybe Bool+ , historicalObservationsMaxResults :: Maybe Integer+ , historicalObservationsRank :: Maybe SelectObservation+ , historicalObservationsSubRegions :: Maybe RegionCode+ , historicalObservationsSPPLocale :: Maybe SPPLocale+ }+ deriving (Show, Read, Eq)++-- | Options for the @recent-checklists@ command.+data RecentChecklistsOptions =+ RecentChecklistsOptions+ { recentChecklistsRegion :: RegionCode+ , recentChecklistsMaxResults :: Maybe Integer+ }+ deriving (Show, Read, Eq)++-- | Options for the @top-100@ command.+data Top100Options =+ Top100Options+ { top100Region :: Region+ , top100Date :: EBirdDate+ , top100RankedBy :: Maybe RankTop100By+ , top100MaxResults :: Maybe Integer+ }+ deriving (Show, Read, Eq)++-- | Options for the @checklist-feed@ command.+data ChecklistFeedOptions =+ ChecklistFeedOptions+ { checklistFeedRegion :: Region+ , checklistFeedDate :: EBirdDate+ , checklistFeedSortBy :: Maybe SortChecklistsBy+ , checklistFeedMaxResults :: Maybe Integer+ }+ deriving (Show, Read, Eq)++-- | Options for the @regional-statistics@ command.+data RegionalStatisticsOptions =+ RegionalStatisticsOptions+ { regionalStatisticsRegion :: Region+ , regionalStatisticsDate :: EBirdDate+ }+ deriving (Show, Read, Eq)++-- | Options for the @species-list@ command.+newtype SpeciesListOptions =+ SpeciesListOptions+ { speciesListRegion :: Region+ }+ deriving (Show, Read, Eq)++-- | Options for the @view-checklist@ command.+newtype ViewChecklistOptions =+ ViewChecklistOptions+ { viewChecklistSubId :: Text+ }+ deriving (Show, Read, Eq)++-- | Options for the @region-hotspots@ command.+data RegionHotspotsOptions =+ RegionHotspotsOptions+ { regionHotspotsRegion :: RegionCode+ , regionHotspotsBack :: Maybe Integer+ , regionHotspotsFmt :: Maybe CSVOrJSONFormat+ }+ deriving (Show, Read, Eq)++-- | Options for the @nearby-hotspots@ command.+data NearbyHotspotsOptions =+ NearbyHotspotsOptions+ { nearbyHotspotsLatitude :: Double+ , nearbyHotspotsLongitude :: Double+ , nearbyHotspotsBack :: Maybe Integer+ , nearbyHotspotsDist :: Maybe Integer+ , nearbyHotspotsFmt :: Maybe CSVOrJSONFormat+ }+ deriving (Show, Read, Eq)++-- | Options for the @hotspot-info@ command.+newtype HotspotInfoOptions =+ HotspotInfoOptions+ { hotspotInfoLocation :: Text+ }+ deriving (Show, Read, Eq)++-- | Options for the @nearby-hotspots@ command.+data TaxonomyOptions =+ TaxonomyOptions+ { taxonomyTaxonomyCategories :: Maybe TaxonomyCategories+ , taxonomyFormat :: Maybe CSVOrJSONFormat+ , taxonomySPPLocale :: Maybe SPPLocale+ , taxonomySpecies :: Maybe SpeciesCodes+ , taxonomyVersion :: Maybe Text+ }+ deriving (Show, Read, Eq)++-- | Options for the @taxonomic-forms@ command.+newtype TaxonomicFormsOptions =+ TaxonomicFormsOptions+ { taxonomicFormsSpecies :: SpeciesCode+ }+ deriving (Show, Read, Eq)++-- | Options for the @taxa-locale-codes@ command.+newtype TaxaLocaleCodesOptions =+ TaxaLocaleCodesOptions+ { taxaLocaleCodesAcceptLanguage :: Maybe SPPLocale+ }+ deriving (Show, Read, Eq)++-- | Options for the @taxonomic-groups@ command.+data TaxonomicGroupsOptions =+ TaxonomicGroupsOptions+ { taxonomicGroupsSPPGrouping :: SPPGrouping+ , taxonomicGroupsSPPLocale :: Maybe SPPLocale+ }+ deriving (Show, Read, Eq)++-- | Options for the @region-info@ command.+data RegionInfoOptions =+ RegionInfoOptions+ { regionInfoRegion :: Region+ , regionInfoRegionNameFormat :: Maybe RegionNameFormat+ }+ deriving (Show, Read, Eq)++-- | Options for the @sub-regions@ command.+data SubRegionListOptions =+ SubRegionListOptions+ { subRegionListParentRegionCode :: RegionCode+ , subRegionListRegionType :: RegionType+ }+ deriving (Show, Read, Eq)++-- | Options for the @sub-regions@ command.+newtype AdjacentRegionsOptions =+ AdjacentRegionsOptions+ { adjacentRegionsRegion :: Region+ }+ deriving (Show, Read, Eq)++-------------------------------------------------------------------------------+-- Command/option/flag parsers+-------------------------------------------------------------------------------++-- | Parse a command provided to the @ebird@ CLI.+eBirdCommand :: Parser (Maybe Text, EBirdCommand)+eBirdCommand =+ (,)+ <$> optionalAPIKey+ <*>+ ( subparser+ ( commandGroup "Observation commands:"+ <> command "observations" recentObservationsInfo+ <> command "notable-observations" recentNotableObservationsInfo+ <> command "species-observations" recentSpeciesObservationsInfo+ <> command "nearby-observations" recentNearbyObservationsInfo+ <> command "nearby-species-observations" recentNearbySpeciesObservationsInfo+ <> command "nearest-species-observations" recentNearestSpeciesObservationsInfo+ <> command "nearby-notable-observations" recentNearbyNotableObservationsInfo+ <> command "historical-observations" historicalObservationsInfo+ )+ <|> subparser+ (+ commandGroup "Product commands:"+ <> command "recent-checklists" recentChecklistsInfo+ <> command "top-100" top100Info+ <> command "checklist-feed" checklistFeedInfo+ <> command "regional-statistics" regionalStatisticsInfo+ <> command "species-list" speciesListInfo+ <> command "view-checklist" viewChecklistInfo+ <> hidden+ )+ <|> subparser+ (+ commandGroup "Hotspot commands:"+ <> command "region-hotspots" regionHotspotsInfo+ <> command "nearby-hotspots" nearbyHotspotsInfo+ <> command "hotspot-info" hotspotInfoInfo+ <> hidden+ )+ <|> subparser+ (+ commandGroup "Taxonomy commands:"+ <> command "taxonomy" taxonomyInfo+ <> command "taxonomic-forms" taxonomicFormsInfo+ <> command "taxa-locale-codes" taxaLocaleCodesInfo+ <> command "taxonomy-versions" taxonomyVersionsInfo+ <> command "taxonomic-groups" taxonomicGroupsInfo+ <> hidden+ )+ <|> subparser+ (+ commandGroup "Region commands:"+ <> command "region-info" regionInfoInfo+ <> command "sub-regions" subRegionsInfo+ <> command "adjacent-regions" adjacentRegionsInfo+ <> hidden+ )+ )+ where+ optionalAPIKey :: Parser (Maybe Text)+ optionalAPIKey =+ optional+ ( strOption+ ( long "api-key"+ <> short 'k'+ <> metavar "API_KEY"+ <> help "Specify an eBird API key"+ )+ )++ recentObservationsInfo :: ParserInfo EBirdCommand+ recentObservationsInfo =+ info+ (RecentObservationsCommand <$> recentObservationsOptions)+ (progDesc "Get recent observations within a region")++ recentNotableObservationsInfo :: ParserInfo EBirdCommand+ recentNotableObservationsInfo =+ info+ (RecentNotableObservationsCommand <$> recentNotableObservationsOptions)+ (progDesc "Get recent notable observations within a region")++ recentSpeciesObservationsInfo :: ParserInfo EBirdCommand+ recentSpeciesObservationsInfo =+ info+ (RecentSpeciesObservationsCommand <$> recentSpeciesObservationsOptions)+ (progDesc "Get recent observations of a species within a region")++ recentNearbyObservationsInfo :: ParserInfo EBirdCommand+ recentNearbyObservationsInfo =+ info+ (RecentNearbyObservationsCommand <$> recentNearbyObservationsOptions)+ (progDesc "Get recent observations within some radius of a latitude/longitude")++ recentNearbySpeciesObservationsInfo :: ParserInfo EBirdCommand+ recentNearbySpeciesObservationsInfo =+ info+ (RecentNearbySpeciesObservationsCommand <$> recentNearbySpeciesObservationsOptions)+ (progDesc "Get recent observations of a species within some radius of a latitude/longitude")++ recentNearestSpeciesObservationsInfo :: ParserInfo EBirdCommand+ recentNearestSpeciesObservationsInfo =+ info+ (RecentNearestSpeciesObservationsCommand <$> recentNearestSpeciesObservationsOptions)+ (progDesc "Get recent observations of a species nearest to a latitude/longitude")++ recentNearbyNotableObservationsInfo :: ParserInfo EBirdCommand+ recentNearbyNotableObservationsInfo =+ info+ (RecentNearbyNotableObservationsCommand <$> recentNearbyNotableObservationsOptions)+ (progDesc "Get recent notable observations within some radius of a latitude/longitude")++ historicalObservationsInfo :: ParserInfo EBirdCommand+ historicalObservationsInfo =+ info+ (HistoricalObservationsCommand <$> historicalObservationsOptions)+ (progDesc "Get a list of observations for each species seen in a region on a specific date")++ recentChecklistsInfo :: ParserInfo EBirdCommand+ recentChecklistsInfo =+ info+ (RecentChecklistsCommand <$> recentChecklistsOptions)+ (progDesc "Get recent checklists within a region")++ top100Info :: ParserInfo EBirdCommand+ top100Info =+ info+ (Top100Command <$> top100Options)+ (progDesc "Get the top 100 contributors in a region for a given date")++ checklistFeedInfo :: ParserInfo EBirdCommand+ checklistFeedInfo =+ info+ (ChecklistFeedCommand <$> checklistFeedOptions)+ (progDesc "Get the checklist feed in a region for a given date")++ regionalStatisticsInfo :: ParserInfo EBirdCommand+ regionalStatisticsInfo =+ info+ (RegionalStatisticsCommand <$> regionalStatisticsOptions)+ (progDesc "Get the regional statistics for a region on a given date")++ speciesListInfo :: ParserInfo EBirdCommand+ speciesListInfo =+ info+ (SpeciesListCommand <$> speciesListOptions)+ (progDesc "Get the list of all species ever observed in a region")++ viewChecklistInfo :: ParserInfo EBirdCommand+ viewChecklistInfo =+ info+ (ViewChecklistCommand <$> viewChecklistOptions)+ (progDesc "Get information about a particular checklist")++ regionHotspotsInfo :: ParserInfo EBirdCommand+ regionHotspotsInfo =+ info+ (RegionHotspotsCommand <$> regionHotspotsOptions)+ (progDesc "Get a list of hotspots in one or more regions")++ nearbyHotspotsInfo :: ParserInfo EBirdCommand+ nearbyHotspotsInfo =+ info+ (NearbyHotspotsCommand <$> nearbyHotspotsOptions)+ (progDesc "Get a list of hotspots within some radius of a latitude/longitude")++ hotspotInfoInfo :: ParserInfo EBirdCommand+ hotspotInfoInfo =+ info+ (HotspotInfoCommand <$> hotspotInfoOptions)+ (progDesc "Get information about a hotspot")++ taxonomyInfo :: ParserInfo EBirdCommand+ taxonomyInfo =+ info+ (TaxonomyCommand <$> taxonomyOptions)+ (progDesc "Get any version of the eBird taxonomy")++ taxonomicFormsInfo :: ParserInfo EBirdCommand+ taxonomicFormsInfo =+ info+ (TaxonomicFormsCommand <$> taxonomicFormsOptions)+ (progDesc "Get the subspecies of a given species recognized by the taxonomy")++ taxaLocaleCodesInfo :: ParserInfo EBirdCommand+ taxaLocaleCodesInfo =+ info+ (TaxaLocaleCodesCommand <$> taxaLocaleCodesOptions)+ (progDesc "Get the supported locale codes and names for species common names")++ taxonomyVersionsInfo :: ParserInfo EBirdCommand+ taxonomyVersionsInfo =+ info+ (pure TaxonomyVersionsCommand <**> helper)+ (progDesc "Get the complete list of taxonomy versions, with a flag indicating which is the latest")++ taxonomicGroupsInfo :: ParserInfo EBirdCommand+ taxonomicGroupsInfo =+ info+ (TaxonomicGroupsCommand <$> taxonomicGroupsOptions)+ (progDesc "Get the list of species groups in either Merlin or eBird grouping")++ regionInfoInfo :: ParserInfo EBirdCommand+ regionInfoInfo =+ info+ (RegionInfoCommand <$> regionInfoOptions)+ (progDesc "Get information about a region")++ subRegionsInfo :: ParserInfo EBirdCommand+ subRegionsInfo =+ info+ (SubRegionListCommand <$> subRegionListOptions)+ (progDesc "Get the list of sub-regions within a region")++ adjacentRegionsInfo :: ParserInfo EBirdCommand+ adjacentRegionsInfo =+ info+ (AdjacentRegionsCommand <$> adjacentRegionsOptions)+ (progDesc "Get the list of regions that are adjacent to a region")++-------------------------------------------------------------------------------+-- Parsers for observation command options+-------------------------------------------------------------------------------++-- | Parse the options for the @recent-observations@ command.+recentObservationsOptions :: Parser RecentObservationsOptions+recentObservationsOptions =+ RecentObservationsOptions+ <$> regionCodeOpt "observations"+ <*> optional (backOpt "observations" "submitted" (Just 14))+ <*> optional (taxonomyCategoriesOpt "observations of")+ <*> optional observationOnlyHotspotsOpt+ <*> optional observationIncludeProvisionalOpt+ <*> optional observationMaxResultsOpt+ <*> optional extraRegionsOpt+ <*> optional (sppLocaleOpt "common")+ <**> helper++-- | Parse the options for a @recent-notable-observations@ command.+recentNotableObservationsOptions :: Parser RecentNotableObservationsOptions+recentNotableObservationsOptions =+ RecentNotableObservationsOptions+ <$> regionCodeOpt "observations"+ <*> optional (backOpt "observations" "submitted" (Just 14))+ <*> optional observationDetailLevelOpt+ <*> optional observationOnlyHotspotsOpt+ <*> optional observationMaxResultsOpt+ <*> optional extraRegionsOpt+ <*> optional (sppLocaleOpt "common")+ <**> helper++-- | Parse the options for a @recent-species-observations@ command.+recentSpeciesObservationsOptions :: Parser RecentSpeciesObservationsOptions+recentSpeciesObservationsOptions =+ RecentSpeciesObservationsOptions+ <$> regionCodeOpt "observations"+ <*> speciesCodeOpt "observations"+ <*> optional (backOpt "observations" "submitted" (Just 14))+ <*> optional observationOnlyHotspotsOpt+ <*> optional observationIncludeProvisionalOpt+ <*> optional observationMaxResultsOpt+ <*> optional extraRegionsOpt+ <*> optional (sppLocaleOpt "common")+ <**> helper++-- | Parse the options for a @recent-nearby-observations@ command.+recentNearbyObservationsOptions :: Parser RecentNearbyObservationsOptions+recentNearbyObservationsOptions =+ RecentNearbyObservationsOptions+ <$> latLngOpt "latitude" "observations"+ <*> latLngOpt "longitude" "observations"+ <*> optional (searchRadiusOpt "observations" 50 (Just 25))+ <*> optional (backOpt "observations" "submitted" (Just 14))+ <*> optional (taxonomyCategoriesOpt "observations of")+ <*> optional observationOnlyHotspotsOpt+ <*> optional observationIncludeProvisionalOpt+ <*> optional observationMaxResultsOpt+ <*> optional observationSortByOpt+ <*> optional (sppLocaleOpt "common")+ <**> helper++-- | Parse the options for a @recent-nearby-species-observations@ command.+recentNearbySpeciesObservationsOptions :: Parser RecentNearbySpeciesObservationsOptions+recentNearbySpeciesObservationsOptions =+ RecentNearbySpeciesObservationsOptions+ <$> speciesCodeOpt "observations"+ <*> latLngOpt "latitude" "observations"+ <*> latLngOpt "longitude" "observations"+ <*> optional (searchRadiusOpt "observations" 50 (Just 25))+ <*> optional (backOpt "observations" "submitted" (Just 14))+ <*> optional (taxonomyCategoriesOpt "observations of")+ <*> optional observationOnlyHotspotsOpt+ <*> optional observationIncludeProvisionalOpt+ <*> optional observationMaxResultsOpt+ <*> optional observationSortByOpt+ <*> optional (sppLocaleOpt "common")+ <**> helper++-- | Parse the options for a @recent-nearest-species-observations@ command.+recentNearestSpeciesObservationsOptions :: Parser RecentNearestSpeciesObservationsOptions+recentNearestSpeciesObservationsOptions =+ RecentNearestSpeciesObservationsOptions+ <$> speciesCodeOpt "observations"+ <*> latLngOpt "latitude" "observations"+ <*> latLngOpt "longitude" "observations"+ <*> optional (searchRadiusOpt "observations" 50 Nothing)+ <*> optional (backOpt "observations" "submitted" (Just 14))+ <*> optional observationOnlyHotspotsOpt+ <*> optional observationIncludeProvisionalOpt+ <*> optional observationMaxResultsOpt+ <*> optional (sppLocaleOpt "common")+ <**> helper++-- | Parse the options for a @recent-nearest-species-observations@ command.+recentNearbyNotableObservationsOptions :: Parser RecentNearbyNotableObservationsOptions+recentNearbyNotableObservationsOptions =+ RecentNearbyNotableObservationsOptions+ <$> latLngOpt "latitude" "observations"+ <*> latLngOpt "longitude" "observations"+ <*> optional (searchRadiusOpt "observations" 50 (Just 25))+ <*> optional observationDetailLevelOpt+ <*> optional (backOpt "observations" "submitted" (Just 14))+ <*> optional observationOnlyHotspotsOpt+ <*> optional observationMaxResultsOpt+ <*> optional (sppLocaleOpt "common")+ <**> helper++-- | Parse the options for a @historical-observations@ command.+historicalObservationsOptions :: Parser HistoricalObservationsOptions+historicalObservationsOptions =+ HistoricalObservationsOptions+ <$> regionCodeOpt "observations"+ <*> dateOpt+ <*> optional (taxonomyCategoriesOpt "observations of")+ <*> optional observationDetailLevelOpt+ <*> optional observationOnlyHotspotsOpt+ <*> optional observationIncludeProvisionalOpt+ <*> optional observationMaxResultsOpt+ <*> optional rankOpt+ <*> optional extraRegionsOpt+ <*> optional (sppLocaleOpt "common")+ <**> helper+ where+ dateOpt :: Parser EBirdDate+ dateOpt =+ option (attoReadM parseEBirdDate)+ ( long "date"+ <> metavar "YYYY-MM-DD"+ <> help ( "Specify the date to fetch observations from (year " +++ "1800 to present)"+ )+ )++ rankOpt :: Parser SelectObservation+ rankOpt =+ option (attoReadM parseSelectObservation)+ ( long "select"+ <> metavar "SELECT"+ <> help ( "Specify whether to select the first or last " +++ "observation of a species if there are multiple " +++ "(\"first\" or \"last\") (default: last)"+ )+ )++-- | Parse a 'GHC.Types.Bool', intended to be used as an option determining whether to+-- include observations from hotspots in the response.+observationOnlyHotspotsOpt :: Parser Bool+observationOnlyHotspotsOpt =+ switch+ ( long "only-hotspots"+ <> help "Only include observations from hotspots"+ )++-- | Parse a 'GHC.Types.Bool', intended to be used as an option determining whether to+-- include unreviewed observations in the response.+observationIncludeProvisionalOpt :: Parser Bool+observationIncludeProvisionalOpt =+ switch+ ( long "include-provisional"+ <> help "Include observations which have not yet been reviewed"+ )++-- | Parse a 'Integer' option indicating the number of results to include in the+-- response.+observationMaxResultsOpt :: Parser Integer+observationMaxResultsOpt =+ option (attoReadM A.decimal)+ ( long "max-results"+ <> metavar "N"+ <> help ( "Specify the max number of observations to include " +++ "(1 to 10000, default: all)"+ )+ )++-- | Parse a 'RegionCode' as a generic command option, intended for use with the+-- observation commands.+regionCodeOpt+ :: String+ -- ^ What are we fetching? (e.g. "observations")+ -> Parser RegionCode+regionCodeOpt thing =+ option (attoReadM parseRegionCode)+ ( long "region"+ <> metavar "REGION_CODE"+ <> help helpStr+ )+ where+ helpStr :: String+ helpStr =+ printf+ ( "Specify the regions to fetch %s from " +++ "(e.g. \"US-WY,US-CO,US-ID\" or \"US-CA-037\")"+ )+ thing++-- | Configurable 'TaxonomyCategories' parser+taxonomyCategoriesOpt+ :: String+ -- ^ String to include after "one or more taxonomy categories to include ..."+ -> Parser TaxonomyCategories+taxonomyCategoriesOpt desc =+ option (attoReadM parseTaxonomyCategories)+ ( long "taxonomy-categories"+ <> metavar "CATEGORIES"+ <> help helpStr+ )+ where+ helpStr :: String+ helpStr =+ printf+ ( "Specify a list of one or more taxonomy categories to include " +++ "%s (e.g. \"issf\" or \"hybrid\") (default: all categories)"+ )+ desc++-- | Parse a 'SortObservationsBy' as an option determining how returned+-- observations will be sorted.+observationSortByOpt :: Parser SortObservationsBy+observationSortByOpt =+ option (attoReadM parseSortObservationsBy)+ ( long "sort-by"+ <> metavar "SORT_BY"+ <> help ( "Specify the ordering to use for the resulting " +++ "observations (\"date\" or \"species\") (default: date, " +++ "earliest first)"+ )+ )++-- | Parse a 'DetailLevel' as an option determining the detail level of+-- observations in the result.+observationDetailLevelOpt :: Parser DetailLevel+observationDetailLevelOpt =+ option (attoReadM parseDetailLevel)+ ( long "detail"+ <> metavar "DETAIL_LEVEL"+ <> help ( "Specify the detail level of the returned observations " +++ "(\"simple\" or \"full\") (default: simple)"+ )+ )++-- | Parse a 'RegionCode' as extra regions to fetch observations from+extraRegionsOpt :: Parser RegionCode+extraRegionsOpt =+ option (attoReadM parseRegionCode)+ ( long "extra-regions"+ <> metavar "REGION_CODE"+ <> help "Up to 10 extra regions to fetch observations from"+ )++-------------------------------------------------------------------------------+-- Parsers for product command options+-------------------------------------------------------------------------------++-- | Parse the options for a @recent-checklists@ command.+recentChecklistsOptions :: Parser RecentChecklistsOptions+recentChecklistsOptions =+ RecentChecklistsOptions+ <$> checklistRegionCodeOpt+ <*> optional checklistMaxResultsOpt+ <**> helper++-- | Parse the options for a @top-100@ command.+top100Options :: Parser Top100Options+top100Options =+ Top100Options+ <$> regionOpt+ <*> dateOpt+ <*> optional rankedByOpt+ <*> optional maxResultsOpt+ <**> helper+ where+ regionOpt :: Parser Region+ regionOpt =+ option (attoReadM parseRegion)+ ( long "region"+ <> metavar "REGION"+ <> help ( "Specify the region to fetch the top 100 contributors " +++ "for (e.g. \"US-WY\" or \"US-CA-037\")"+ )+ )++ dateOpt :: Parser EBirdDate+ dateOpt =+ option (attoReadM parseEBirdDate)+ ( long "date"+ <> metavar "YYYY-MM-DD"+ <> help ( "Specify the date to fetch the top contributors on " +++ "(year 1800 to present)"+ )+ )++ rankedByOpt :: Parser RankTop100By+ rankedByOpt =+ option (attoReadM parseRankTop100By)+ ( long "rank-by"+ <> metavar "RANK_BY"+ <> help ( "Specify whether to rank contributors by number of " +++ "species observed (\"spp\") or number of checklists " +++ "completed (\"cl\") (default: spp)"+ )+ )++ maxResultsOpt :: Parser Integer+ maxResultsOpt =+ option (attoReadM A.decimal)+ ( long "max-results"+ <> metavar "N"+ <> help ( "Specify the max number of contributors to include " +++ "(1 to 100, default: 100)"+ )+ )++-- | Parse the options for a @top-100@ command.+checklistFeedOptions :: Parser ChecklistFeedOptions+checklistFeedOptions =+ ChecklistFeedOptions+ <$> regionOpt+ <*> dateOpt+ <*> optional sortByOpt+ <*> optional maxResultsOpt+ <**> helper+ where+ regionOpt :: Parser Region+ regionOpt =+ option (attoReadM parseRegion)+ ( long "region"+ <> metavar "REGION"+ <> help ( "Specify the region to fetch the checklist feed " +++ "for (e.g. \"US-WY\" or \"US-CA-037\")"+ )+ )++ dateOpt :: Parser EBirdDate+ dateOpt =+ option (attoReadM parseEBirdDate)+ ( long "date"+ <> metavar "YYYY-MM-DD"+ <> help ( "Specify the date to fetch the checklist feed on " +++ "(year 1800 to present)"+ )+ )++ sortByOpt :: Parser SortChecklistsBy+ sortByOpt =+ option (attoReadM parseSortChecklistsBy)+ ( long "sort-by"+ <> metavar "SORT_BY"+ <> help ( "Specify whether to sort the checklist fee by date of " +++ "creation (\"obs_dt\") or date of submission " +++ "(\"creation_dt\") (default: obs_dt)"+ )+ )++ maxResultsOpt :: Parser Integer+ maxResultsOpt =+ option (attoReadM A.decimal)+ ( long "max-results"+ <> metavar "N"+ <> help ( "Specify the max number of checklists to include " +++ "(1 to 200, default: 10)"+ )+ )++-- | Parse the options for a @regional-statistics@ command.+regionalStatisticsOptions :: Parser RegionalStatisticsOptions+regionalStatisticsOptions =+ RegionalStatisticsOptions+ <$> regionOpt+ <*> dateOpt+ <**> helper+ where+ regionOpt :: Parser Region+ regionOpt =+ option (attoReadM parseRegion)+ ( long "region"+ <> metavar "REGION"+ <> help ( "Specify the region to fetch the statistics for " +++ "(e.g. \"US-WY\" or \"US-CA-037\")"+ )+ )++ dateOpt :: Parser EBirdDate+ dateOpt =+ option (attoReadM parseEBirdDate)+ ( long "date"+ <> metavar "YYYY-MM-DD"+ <> help ( "Specify the date to fetch the statistics on (year " +++ "1800 to present)"+ )+ )++-- | Parse the options for a @regional-statistics@ command.+speciesListOptions :: Parser SpeciesListOptions+speciesListOptions =+ SpeciesListOptions+ <$> regionOpt+ <**> helper+ where+ regionOpt :: Parser Region+ regionOpt =+ option (attoReadM parseRegion)+ ( long "region"+ <> metavar "REGION"+ <> help ( "Specify the region to fetch the species list for " +++ "(e.g. \"US-WY\" or \"US-CA-037\")"+ )+ )++-- | Parse the options for a @regional-statistics@ command.+viewChecklistOptions :: Parser ViewChecklistOptions+viewChecklistOptions =+ ViewChecklistOptions+ <$> subIdOpt+ <**> helper+ where+ subIdOpt :: Parser Text+ subIdOpt =+ option (attoReadM A.takeText)+ ( long "submission-id"+ <> metavar "SUBMISSION_ID"+ <> help ( "Specify the submission ID of the checklist to view " +++ "(e.g. \"S144646447\")"+ )+ )++-- | Parse a 'RegionCode' as a generic command option, intended for use with the+-- checklists commands.+checklistRegionCodeOpt :: Parser RegionCode+checklistRegionCodeOpt =+ option (attoReadM parseRegionCode)+ ( long "region"+ <> metavar "REGION_CODE"+ <> help ( "Specify the regions to fetch checklists from " +++ "(e.g. \"US-WY,US-CO\" or \"US-CA-037\")"+ )+ )++-- | Parse a 'Integer' as a generic command option, intended for use with the+-- checklists commands for determining max results to include.+checklistMaxResultsOpt :: Parser Integer+checklistMaxResultsOpt =+ option (attoReadM A.decimal)+ ( long "max-results"+ <> metavar "N"+ <> help ( "Specify the max number of checklists to include " +++ "(1 to 200, default: 10)"+ )+ )++-------------------------------------------------------------------------------+-- Parsers for hotspot command options+-------------------------------------------------------------------------------++-- | Parse the options for a @region-hotspots@ command.+regionHotspotsOptions :: Parser RegionHotspotsOptions+regionHotspotsOptions =+ RegionHotspotsOptions+ <$> regionCodeOpt "hotspots"+ <*> optional (backOpt "hotspots" "visited" Nothing)+ <*> pure (Just JSONFormat)+ <**> helper++-- | Parse the options for a @nearby-hotspots@ command.+nearbyHotspotsOptions :: Parser NearbyHotspotsOptions+nearbyHotspotsOptions =+ NearbyHotspotsOptions+ <$> latLngOpt "latitude" "hotspots"+ <*> latLngOpt "longitude" "hotspots"+ <*> optional (backOpt "hotspots" "visited" Nothing)+ <*> optional (searchRadiusOpt "hotspots" 50 (Just 25))+ <*> pure (Just JSONFormat)+ <**> helper++-- | Parse the options for a @hotspot-info@ command.+hotspotInfoOptions :: Parser HotspotInfoOptions+hotspotInfoOptions =+ HotspotInfoOptions+ <$> locationCodeOpt+ <**> helper+ where+ locationCodeOpt :: Parser Text+ locationCodeOpt =+ option (attoReadM A.takeText)+ ( long "location"+ <> metavar "LOCATION"+ <> help "Location code of the hotspot (e.g. \"L5044136\")"+ )+-------------------------------------------------------------------------------+-- Parsers for taxonomy command options+-------------------------------------------------------------------------------++-- | Parse the options for a @nearby-hotspots@ command.+taxonomyOptions :: Parser TaxonomyOptions+taxonomyOptions =+ TaxonomyOptions+ <$> optional (taxonomyCategoriesOpt "in the taxonomy")+ <*> pure (Just JSONFormat)+ <*> optional (sppLocaleOpt "common")+ <*> optional speciesCodesOpt+ <*> optional taxonomyVersionOpt+ <**> helper+ where+ speciesCodesOpt :: Parser SpeciesCodes+ speciesCodesOpt =+ option (attoReadM parseSpeciesCodes)+ ( long "species"+ <> metavar "SPECIES_CODES"+ <> help "Only include entries for these species (default: all)"+ )++ taxonomyVersionOpt :: Parser Text+ taxonomyVersionOpt =+ option (attoReadM A.takeText)+ ( long "version"+ <> metavar "VERSION"+ <> help "Taxonomy version to fetch (default: latest)"+ )++-- | Parse the options for a @taxonomic-forms@ command.+taxonomicFormsOptions :: Parser TaxonomicFormsOptions+taxonomicFormsOptions =+ TaxonomicFormsOptions+ <$> speciesCodeOpt "subspecies"+ <**> helper++-- | Parse the options for a @taxa-locale-codes@ command.+taxaLocaleCodesOptions :: Parser TaxaLocaleCodesOptions+taxaLocaleCodesOptions =+ TaxaLocaleCodesOptions+ <$> optional acceptLanguageOpt+ <**> helper+ where+ acceptLanguageOpt :: Parser SPPLocale+ acceptLanguageOpt =+ option (attoReadM parseSPPLocale)+ ( long "accept-language"+ <> metavar "LOCALE"+ <> help ( "Get language names translated to this locale, when " +++ "available (default: en)"+ )+ )++-- | Parse the options for a @taxa-locale-codes@ command.+taxonomicGroupsOptions :: Parser TaxonomicGroupsOptions+taxonomicGroupsOptions =+ TaxonomicGroupsOptions+ <$> sppGroupingOpt+ <*> optional (sppLocaleOpt "group")+ <**> helper+ where+ sppGroupingOpt :: Parser SPPGrouping+ sppGroupingOpt =+ option (attoReadM parseSPPGrouping)+ ( long "spp-grouping"+ <> metavar "GROUPING"+ <> help ( "Group species using Merlin or eBird grouping " +++ "(\"merlin\" or \"ebird\")"+ )+ )++-------------------------------------------------------------------------------+-- Parsers for region command options+-------------------------------------------------------------------------------++-- | Parse the options for the @region-info@ command.+regionInfoOptions :: Parser RegionInfoOptions+regionInfoOptions =+ RegionInfoOptions+ <$> region+ <*> optional regionNameFormat+ <**> helper++-- | Parse the options for the @sub-regions@ command.+subRegionListOptions :: Parser SubRegionListOptions+subRegionListOptions =+ SubRegionListOptions+ <$> regionCode+ <*> regionType+ <**> helper++-- | Parse the options for the @sub-regions@ command.+adjacentRegionsOptions :: Parser AdjacentRegionsOptions+adjacentRegionsOptions =+ AdjacentRegionsOptions+ <$> regionOpt+ <**> helper+ where+ regionOpt :: Parser Region+ regionOpt =+ option (attoReadM parseRegion)+ ( long "region"+ <> metavar "REGION"+ <> help ( "Specify the region to get adjacent regions of " +++ "(e.g. \"US-WY\")"+ )+ )++-------------------------------------------------------------------------------+-- Generic single option parsers+-------------------------------------------------------------------------------++-- | Parse a 'SPPLocale' as a generic command option.+sppLocaleOpt :: String -> Parser SPPLocale+sppLocaleOpt nameType =+ option (attoReadM parseSPPLocale)+ ( long "spp-locale"+ <> metavar "LOCALE"+ <> help helpStr+ )+ where+ helpStr :: String+ helpStr =+ printf+ "Specify a locale to use for %s names"+ nameType++-- | Parse a 'Region' as a generic command option.+region :: Parser Region+region = option (attoReadM parseRegion)+ ( long "region"+ <> metavar "REGION"+ <> help "Specify a region (e.g. \"world\" or \"US-WY\")"+ )++-- | Parse a 'RegionCode' as a generic command option.+regionCode :: Parser RegionCode+regionCode = option (attoReadM parseRegionCode)+ ( long "region"+ <> metavar "REGION_CODE"+ <> help "Specify a region code (e.g. \"world\" or \"US-MT,US-WY\")"+ )++-- | Parse a 'RegionNameFormat' as a generic command option.+regionNameFormat :: Parser RegionNameFormat+regionNameFormat = option (attoReadM parseRegionNameFormat)+ ( long "region-name-format"+ <> metavar "REGION_NAME_FORMAT"+ <> help ( "Specify a region name format for the result. Must be one " +++ "of \"detailed\", \"detailednoqual\", \"full\", " +++ "\"namequal\", \"nameonly\", or \"revdetailed\" " +++ "(default: full)"+ )+ )++-- | Parse a 'RegionType' as a generic command option.+regionType :: Parser RegionType+regionType = option (attoReadM parseRegionType)+ ( long "region-type"+ <> metavar "REGION_TYPE"+ <> help ( "Specify a region type (\"country\", \"subnational1\", " +++ "\"subnational2\")"+ )+ )++-------------------------------------------------------------------------------+-- Utility functions+-------------------------------------------------------------------------------++-- | Configurable 'SpeciesCode' option+speciesCodeOpt :: String -> Parser SpeciesCode+speciesCodeOpt thing =+ option (attoReadM parseSpeciesCode)+ ( long "species-code"+ <> metavar "SPECIES_CODE"+ <> help helpStr+ )+ where+ helpStr :: String+ helpStr =+ printf+ ( "Specify a species code to fetch %s for (e.g. \"barswa\" for Barn " +++ "Swallow)"+ )+ thing++-- | Configurable "search radius" option+searchRadiusOpt+ :: String+ -- ^ What are we searching for (e.g. "observations")+ -> Integer+ -- ^ Maximum allowed value by the API+ -> Maybe Integer+ -- ^ Default value of the API ('Nothing' for "no limit")+ -> Parser Integer+searchRadiusOpt thing maxRadius mDefaultRadius =+ option (attoReadM A.decimal)+ ( long "radius"+ <> metavar "KILOMETERS"+ <> help helpStr+ )+ where+ helpStr :: String+ helpStr =+ printf+ "Specify the search radius to fetch %s within (0 to %d, default: %s)"+ thing+ maxRadius+ defStr++ defStr = maybe "no limit" show mDefaultRadius++-- | Configurable "back" option+backOpt+ :: String+ -- ^ What are we fetching (e.g. "hotspots")+ -> String+ -- ^ Verb we are filtering on (e.g. "submitted", or "visited")+ -> Maybe Integer+ -- ^ Default value ('Nothing' for "no limit")+ -> Parser Integer+backOpt thing verb mmax =+ option (attoReadM A.decimal)+ ( long "back"+ <> metavar "N"+ <> help helpStr+ )+ where+ helpStr :: String+ helpStr =+ printf+ "Only fetch %s %s within the last N days (1 - 30, default: %s)"+ thing+ verb+ defStr++ defStr = maybe "no limit" show mmax++-- | Configurable lat/lng option+latLngOpt+ :: String+ -- ^ "latitude" or "longitude"+ -> String+ -- ^ What are we looking for+ -> Parser Double+latLngOpt latLng thing =+ option (attoReadM A.double)+ ( long latLng+ <> metavar (map toUpper latLng)+ <> help helpStr+ )+ where+ helpStr :: String+ helpStr =+ printf+ "Specify the %s of the location to fetch %s near"+ latLng+ thing++-- Attoparsec utilities++-- | Convert an attoparsec parser into an optparse-applicative parser.+attoReadM :: A.Parser a -> ReadM a+attoReadM p = eitherReader (A.parseOnly p . Text.pack)