1 -- | The CommandLine module handles parsing of the command-line
2 -- options. It should more or less be a black box, providing Main
3 -- with only the information it requires.
12 import Data.Maybe (fromJust, isJust, isNothing)
13 import System.Console.GetOpt
14 import System.Directory (doesFileExist)
15 import System.Environment (getArgs)
17 import Configuration (Cfg(..))
19 -- | A record containing values for all available options.
20 data Options = Options { opt_heartbeat :: Maybe Int,
22 opt_ignore_replies :: Bool,
23 opt_ignore_retweets :: Bool,
24 opt_sendmail_path :: FilePath,
25 opt_from :: Maybe String,
26 opt_to :: Maybe String,
30 -- | Constructs an instance of Options, with each of its members set
32 default_options :: Options
33 default_options = Options { opt_heartbeat = Just 600,
35 opt_ignore_replies = False,
36 opt_ignore_retweets = False,
37 opt_sendmail_path = "/usr/sbin/sendmail",
43 -- | The options list that we construct associates a function with
44 -- each option. This function is responsible for updating an Options
45 -- record with the appropriate value.
47 -- For more information and an example of this idiom, see,
49 -- <http://www.haskell.org/haskellwiki/High-level_option_handling_with_GetOpt>
51 options :: [OptDescr (Options -> IO Options)]
56 "Prints this help message.",
60 (ReqArg set_heartbeat "heartbeat")
61 "How many seconds to wait between polling.",
65 (ReqArg set_to "email_address")
66 "Send tweets TO email_address.",
70 (ReqArg set_from "email_address")
71 "Send tweets FROM email_address.",
74 ['s']["sendmail_path"]
75 (ReqArg set_sendmail_path "sendmail_path")
76 "Use sendmail_path to send mail",
79 ['i']["ignore-replies"]
80 (NoArg set_ignore_replies)
84 ['I']["ignore-retweets"]
85 (NoArg set_ignore_retweets)
91 "Be verbose about stuff."
95 -- | Attempt to parse an 'Int' from a 'String'. This is just a 'Maybe'
96 -- wrapper around 'reads'.
97 parse_int :: String -> Maybe Int
103 set_heartbeat :: String -> Options -> IO Options
104 set_heartbeat arg opts = do
105 let new_heartbeat = parse_int arg
106 return opts { opt_heartbeat = new_heartbeat }
108 set_help :: Options -> IO Options
110 return opts { opt_help = True }
112 set_ignore_retweets :: Options -> IO Options
113 set_ignore_retweets opts =
114 return opts { opt_ignore_retweets = True }
116 set_ignore_replies :: Options -> IO Options
117 set_ignore_replies opts =
118 return opts { opt_ignore_replies = True }
120 set_verbose :: Options -> IO Options
122 return opts { opt_verbose = True }
124 set_sendmail_path :: String -> Options -> IO Options
125 set_sendmail_path arg opts = do
126 return opts { opt_sendmail_path = arg }
128 set_to :: String -> Options -> IO Options
130 return opts { opt_to = Just arg }
132 set_from :: String -> Options -> IO Options
133 set_from arg opts = do
134 return opts { opt_from = Just arg }
137 -- | The usage header.
139 usage = "Usage: twat [-n heartbeat] [-t to_address] [-f from_address] [-s path-to-sendmail] <username1> [username2, [username3]...]"
142 -- | Was the help option passed?
145 opts <- parse_options
146 return (opt_help opts)
148 -- | The usage header, and all available flags (as generated by GetOpt)
150 help_text = usageInfo usage options
153 -- | Return a list of options.
154 parse_options :: IO Options
157 let (actions, _, _) = getOpt Permute options argv
159 -- This will execute each of the functions contained in our options
160 -- list, one after another, on a default_options record. The end
161 -- result should be an Options instance with all of its members set
163 opts <- foldl (>>=) (return default_options) actions
168 -- | A list of parse errors relating to the heartbeat.
169 heartbeat_errors :: IO [String]
170 heartbeat_errors = do
171 hb <- parse_heartbeat
173 then return ["\"heartbeat\" does not appear to be an integer."]
176 -- | Parse errors relating to the list of usernames.
177 username_errors :: IO [String]
180 let (_, usernames, _) = getOpt Permute options argv
183 then return ["no usernames provided."]
187 -- | Parse errors relating to the "To" address.
188 to_errors :: IO [String]
190 toaddr <- parse_to_address
191 fromaddr <- parse_from_address
192 if (isNothing toaddr) && (isJust fromaddr)
193 then return ["\"from\" address specified without \"to\" address."]
197 -- | Errors for the sendmail path argument.
198 sendmail_path_errors :: IO [String]
199 sendmail_path_errors = do
200 sendmail <- parse_sendmail_path
201 exists <- doesFileExist sendmail
203 then return ["sendmail path does not exist"]
207 -- | Parse errors relating to the "From" address.
208 from_errors :: IO [String]
210 toaddr <- parse_to_address
211 fromaddr <- parse_from_address
212 if (isJust toaddr) && (isNothing fromaddr)
213 then return ["\"to\" address specified without \"from\" address."]
217 -- | Format an error message for printing.
218 format_error :: String -> String
219 format_error err = "ERROR: " ++ err ++ "\n"
222 -- | Return a list of all parse errors.
223 parse_errors :: IO [String]
226 let (_, _, errors) = getOpt Permute options argv
227 errs_heartbeat <- heartbeat_errors
228 errs_username <- username_errors
230 errs_from <- from_errors
231 errs_sendmail <- sendmail_path_errors
232 return $ map format_error (errors ++
239 -- | What's the heartbeat?
240 parse_heartbeat :: IO (Maybe Int)
242 opts <- parse_options
243 return (opt_heartbeat opts)
245 -- | What "To" address was given on the command line?
246 parse_to_address :: IO (Maybe String)
247 parse_to_address = do
248 opts <- parse_options
251 -- | What sendmail path was given on the command line?
252 parse_sendmail_path :: IO FilePath
253 parse_sendmail_path = do
254 opts <- parse_options
255 return (opt_sendmail_path opts)
257 -- | What "From" address was given on the command line?
258 parse_from_address :: IO (Maybe String)
259 parse_from_address = do
260 opts <- parse_options
261 return (opt_from opts)
264 -- | What usernames were passed on the command line?
265 parse_usernames :: IO [String]
268 let (_, usernames, _) = getOpt Permute options argv
273 -- | Construct a Cfg object from the command line options assuming
274 -- there are no errors.
277 opts <- parse_options
278 return Cfg { heartbeat = fromJust $ opt_heartbeat opts,
279 ignore_replies = opt_ignore_replies opts,
280 ignore_retweets = opt_ignore_retweets opts,
281 sendmail_path = opt_sendmail_path opts,
282 from_address = opt_from opts,
283 to_address = opt_to opts,
284 verbose = opt_verbose opts }