]> gitweb.michael.orlitzky.com - dead/halcyon.git/blob - src/CommandLine.hs
Whitespace cleanup.
[dead/halcyon.git] / src / CommandLine.hs
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.
4 module CommandLine
5 ( get_cfg,
6 help_set,
7 help_text,
8 parse_errors,
9 parse_usernames
10 ) where
11
12 import Data.Maybe (fromJust, isJust, isNothing)
13 import System.Console.GetOpt
14 import System.Directory (doesFileExist)
15 import System.Environment (getArgs)
16
17 import Configuration (Cfg(..))
18
19 -- | A record containing values for all available options.
20 data Options = Options { opt_heartbeat :: Maybe Int,
21 opt_help :: Bool,
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,
27 opt_verbose :: Bool }
28
29
30 -- | Constructs an instance of Options, with each of its members set
31 -- to default values.
32 default_options :: Options
33 default_options = Options { opt_heartbeat = Just 600,
34 opt_help = False,
35 opt_ignore_replies = False,
36 opt_ignore_retweets = False,
37 opt_sendmail_path = "/usr/sbin/sendmail",
38 opt_from = Nothing,
39 opt_to = Nothing,
40 opt_verbose = False }
41
42
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.
46 --
47 -- For more information and an example of this idiom, see,
48 --
49 -- <http://www.haskell.org/haskellwiki/High-level_option_handling_with_GetOpt>
50 --
51 options :: [OptDescr (Options -> IO Options)]
52 options =
53 [ Option
54 ['h']["help"]
55 (NoArg set_help)
56 "Prints this help message.",
57
58 Option
59 ['n']["heartbeat"]
60 (ReqArg set_heartbeat "heartbeat")
61 "How many seconds to wait between polling.",
62
63 Option
64 ['t']["to"]
65 (ReqArg set_to "email_address")
66 "Send tweets TO email_address.",
67
68 Option
69 ['f']["from"]
70 (ReqArg set_from "email_address")
71 "Send tweets FROM email_address.",
72
73 Option
74 ['s']["sendmail_path"]
75 (ReqArg set_sendmail_path "sendmail_path")
76 "Use sendmail_path to send mail",
77
78 Option
79 ['i']["ignore-replies"]
80 (NoArg set_ignore_replies)
81 "Ignore replies.",
82
83 Option
84 ['I']["ignore-retweets"]
85 (NoArg set_ignore_retweets)
86 "Ignore retweets.",
87
88 Option
89 ['v']["verbose"]
90 (NoArg set_verbose)
91 "Be verbose about stuff."
92 ]
93
94
95 -- | Attempt to parse an 'Int' from a 'String'. This is just a 'Maybe'
96 -- wrapper around 'reads'.
97 parse_int :: String -> Maybe Int
98 parse_int s =
99 case (reads s) of
100 [(n,_)] -> Just n
101 _ -> Nothing
102
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 }
107
108 set_help :: Options -> IO Options
109 set_help opts = do
110 return opts { opt_help = True }
111
112 set_ignore_retweets :: Options -> IO Options
113 set_ignore_retweets opts =
114 return opts { opt_ignore_retweets = True }
115
116 set_ignore_replies :: Options -> IO Options
117 set_ignore_replies opts =
118 return opts { opt_ignore_replies = True }
119
120 set_verbose :: Options -> IO Options
121 set_verbose opts =
122 return opts { opt_verbose = True }
123
124 set_sendmail_path :: String -> Options -> IO Options
125 set_sendmail_path arg opts = do
126 return opts { opt_sendmail_path = arg }
127
128 set_to :: String -> Options -> IO Options
129 set_to arg opts = do
130 return opts { opt_to = Just arg }
131
132 set_from :: String -> Options -> IO Options
133 set_from arg opts = do
134 return opts { opt_from = Just arg }
135
136
137 -- | The usage header.
138 usage :: String
139 usage = "Usage: twat [-n heartbeat] [-t to_address] [-f from_address] [-s path-to-sendmail] <username1> [username2, [username3]...]"
140
141
142 -- | Was the help option passed?
143 help_set :: IO Bool
144 help_set = do
145 opts <- parse_options
146 return (opt_help opts)
147
148 -- | The usage header, and all available flags (as generated by GetOpt)
149 help_text :: String
150 help_text = usageInfo usage options
151
152
153 -- | Return a list of options.
154 parse_options :: IO Options
155 parse_options = do
156 argv <- getArgs
157 let (actions, _, _) = getOpt Permute options argv
158
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
162 -- correctly.
163 opts <- foldl (>>=) (return default_options) actions
164
165 return opts
166
167
168 -- | A list of parse errors relating to the heartbeat.
169 heartbeat_errors :: IO [String]
170 heartbeat_errors = do
171 hb <- parse_heartbeat
172 if (isNothing hb)
173 then return ["\"heartbeat\" does not appear to be an integer."]
174 else return []
175
176 -- | Parse errors relating to the list of usernames.
177 username_errors :: IO [String]
178 username_errors = do
179 argv <- getArgs
180 let (_, usernames, _) = getOpt Permute options argv
181
182 if (null usernames)
183 then return ["no usernames provided."]
184 else return []
185
186
187 -- | Parse errors relating to the "To" address.
188 to_errors :: IO [String]
189 to_errors = do
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."]
194 else return []
195
196
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
202 if (not exists)
203 then return ["sendmail path does not exist"]
204 else return []
205
206
207 -- | Parse errors relating to the "From" address.
208 from_errors :: IO [String]
209 from_errors = do
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."]
214 else return []
215
216
217 -- | Format an error message for printing.
218 format_error :: String -> String
219 format_error err = "ERROR: " ++ err ++ "\n"
220
221
222 -- | Return a list of all parse errors.
223 parse_errors :: IO [String]
224 parse_errors = do
225 argv <- getArgs
226 let (_, _, errors) = getOpt Permute options argv
227 errs_heartbeat <- heartbeat_errors
228 errs_username <- username_errors
229 errs_to <- to_errors
230 errs_from <- from_errors
231 errs_sendmail <- sendmail_path_errors
232 return $ map format_error (errors ++
233 errs_heartbeat ++
234 errs_username ++
235 errs_sendmail ++
236 errs_to ++
237 errs_from)
238
239 -- | What's the heartbeat?
240 parse_heartbeat :: IO (Maybe Int)
241 parse_heartbeat = do
242 opts <- parse_options
243 return (opt_heartbeat opts)
244
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
249 return (opt_to opts)
250
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)
256
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)
262
263
264 -- | What usernames were passed on the command line?
265 parse_usernames :: IO [String]
266 parse_usernames = do
267 argv <- getArgs
268 let (_, usernames, _) = getOpt Permute options argv
269 return usernames
270
271
272
273 -- | Construct a Cfg object from the command line options assuming
274 -- there are no errors.
275 get_cfg :: IO Cfg
276 get_cfg = do
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 }