packages feed

weatherhs-1.0.0: app/ArgParser.hs

{-
 - Copyright (C) 2022 Nikola Hadžić
 -
 - This file is part of weatherhs.
 -
 - weatherhs is free software: you can redistribute it and/or modify
 - it under the terms of the GNU General Public License as published by
 - the Free Software Foundation, either version 3 of the License, or
 - (at your option) any later version.
 - 
 - weatherhs is distributed in the hope that it will be useful,
 - but WITHOUT ANY WARRANTY; without even the implied warranty of
 - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 - GNU General Public License for more details.
 - 
 - You should have received a copy of the GNU General Public License
 - along with weatherhs.  If not, see <https://www.gnu.org/licenses/>.
 -}

module ArgParser where

import              Data.List               (intercalate)
import              Text.Read               (readMaybe)
import              Text.Printf             (printf)
import              Text.Regex.PCRE         ((=~))
import              System.Environment      (getProgName)
import qualified    System.Console.GetOpt   as GetOpt

import GetText

-- Command-line options flags.
data Flag = Help | Version | NoPP | Mode String | AirQuality | Alerts | Days String | Date String | Format String | Unsafe | GetFile
    deriving (Show, Eq)

-- Command-line options definitions.
options :: [GetOpt.OptDescr Flag]
options = [ GetOpt.Option ['h']    ["help"]        (GetOpt.NoArg Help)                    (__ "Display this help menu and exit."),
            GetOpt.Option ['v']    ["version"]     (GetOpt.NoArg Version)                 (__ "Display program version and exit."),
            GetOpt.Option []       ["no-pp"]       (GetOpt.NoArg NoPP)                    (__ "Do not pretty-print data."),
            GetOpt.Option ['m']    ["mode"]        (GetOpt.ReqArg Mode (__ "MODE"))       (__ "Information type mode. Can be \"timezone\", \"current\" (default), \"forecast\" or \"astronomy\"."),
            GetOpt.Option ['a']    ["air-quality"] (GetOpt.NoArg AirQuality)              (__ "Also get air quaility data. Only meaningful if the information type mode is \"current\" or \"forecast\"."),
            GetOpt.Option ['w']    ["alerts"]      (GetOpt.NoArg Alerts)                  (__ "Also get weather alerts data. Only meaningful if the information type mode is \"forecast\"."),
            GetOpt.Option ['d']    ["days"]        (GetOpt.ReqArg Days (__ "INTEGER≥1"))  (__ "Number of days to retrieve data for. Only meaningful if the information type mode is \"forecast\". The default is 3."),
            GetOpt.Option ['t']    ["date"]        (GetOpt.ReqArg Date (__ "yyyy-MM-dd")) (__ "Date to retrieve the data for. Only meaningful if the information type mode is \"astronomy\". The default is current date."),
            GetOpt.Option []       ["format"]      (GetOpt.ReqArg Format (__ "FORMAT"))   (__ "Format to request data in from the server. Either \"json\" (default) or \"xml\"."),
            GetOpt.Option []       ["unsafe"]      (GetOpt.NoArg Unsafe)                  (__ "Use HTTP instead of HTTPS for communication with the server."),
            GetOpt.Option []       ["get-file"]    (GetOpt.NoArg GetFile)                 (__ "Just print the retrieved data file.") ]

-- Help menu string.
helpMenu :: IO String
helpMenu = do
    progName <- getProgName
    return ((GetOpt.usageInfo (printf (__ "Usage: %s [OPTION…]\n\nOptions:") progName) options) ++ "\n" ++ __ "See weatherhs(1) for more information.")

-- Tests the validity of the given command-line options values.
testFlags :: [Flag] -> [String]
testFlags flags = foldr
    (\flag invs -> case flag of
                    Mode str    -> if str == "timezone" || str == "current" || str == "forecast" || str == "astronomy" then invs else "-m/--mode" : invs
                    Days str    -> case (readMaybe str :: Maybe Int) of
                                    Nothing     -> "-d/--days" : invs
                                    Just int    -> if (int <= 0) then "-d/--days" : invs else invs
                    Date str    -> if str =~ "^\\d{4,}-\\d{1,2}-\\d{1,2}$" then invs else "-t/--date" : invs
                    Format str  -> if str == "json" || str == "xml" then invs else "--format" : invs
                    _           -> invs) [] flags

-- Parses the given command-line arguments.
-- Returns the parsed command-line arguments as flags.
-- Exits the program on invalid input.
parseArgs :: [String] -> IO [Flag]
parseArgs argv = case GetOpt.getOpt' GetOpt.RequireOrder options argv of
                    (flags, [], [], [])             -> case testFlags flags of
                                                        []      -> return flags
                                                        invs    -> fail (printf (__ "Invalid option values for: %s") (intercalate ", " invs))
                    (_, nonoptions, unknowns, [])   -> fail (printf (__ "Unrecognized options: %s") (intercalate ", " (nonoptions ++ unknowns)))
                    (_, _, _, errs)                 -> fail (printf (__ "Errors: %s") (intercalate "; " errs))