From 60c61e5eb68575f723beac12fc3162ab30557953 Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Wed, 28 Apr 2010 22:20:56 -0400 Subject: [PATCH] Add a CommandLine module for parsing command-line options. Update the Main module to use the new CommandLine module. Implement an input file option (to override stdin). --- src/CommandLine.hs | 98 ++++++++++++++++++++++++++++++++++++++++++++++ src/Main.hs | 45 ++++++++++++++++++--- 2 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 src/CommandLine.hs diff --git a/src/CommandLine.hs b/src/CommandLine.hs new file mode 100644 index 0000000..eba70db --- /dev/null +++ b/src/CommandLine.hs @@ -0,0 +1,98 @@ +-- The CommandLine module handles parsing of the command-line options. +-- It should more or less be a black box, providing Main with only the +-- information it requires. + +module CommandLine +( help_set, + help_text, + input_function, + parse_errors +) where + +import System.Console.GetOpt +import System.Environment (getArgs) + + +-- A record containing values for all available options. +data Options = Options { opt_help :: Bool, + opt_input :: IO String } + +-- This constructs an instance of Options, with each of its members +-- set to default values. +default_options :: Options +default_options = Options { opt_help = False, + opt_input = getContents } + + +-- The options list that we construct associates a function with each +-- option. This function is responsible for updating an Options record +-- with the appropriate value. +-- +-- For more information and an example of this idiom, see, +-- +-- http://www.haskell.org/haskellwiki/High-level_option_handling_with_GetOpt +-- +options :: [OptDescr (Options -> IO Options)] +options = + [ Option ['h'][] (NoArg set_help) "Prints this help message.", + Option ['i'][] (ReqArg set_input "FILE") "Read FILE instead of stdin." ] + +-- Takes an Options as an argument, and sets its opt_help member to +-- True. +set_help :: Options -> IO Options +set_help opts = do + return opts { opt_help = True } + +-- If the input file option is set, this function will update the +-- passed Options record with a new function for opt_input. The +-- default opt_input is to read from stdin, but if this option is set, +-- we replace that with readFile. +set_input :: String -> Options -> IO Options +set_input arg opts = do + return opts { opt_input = readFile arg } + +-- The usage header +usage :: String +usage = "Usage: hath [-h] [-i FILE]" + + +-- The usage header, and all available flags (as generated by GetOpt) +help_text :: String +help_text = usageInfo usage options + + +-- Return a tuple of options and errors. +parse_options :: IO (Options, [String]) +parse_options = do + argv <- getArgs + let (actions, _, errors) = getOpt Permute options argv + + -- This will execute each of the functions contained in our options + -- list, one after another, on a default_options record. The end + -- result should be an Options instance with all of its members set + -- correctly. + opts <- foldl (>>=) (return default_options) actions + + return (opts, errors) + + +-- Return just the errors from parse_options. +parse_errors :: IO [String] +parse_errors = do + (_, errors) <- parse_options + return errors + + +-- Is the help option set? +help_set :: IO Bool +help_set = do + (opts, _) <- parse_options + return (opt_help opts) + + +-- Return our input function, getContents by default, or readFile if +-- the input file option was set. +input_function :: IO (IO String) +input_function = do + (opts, _) <- parse_options + return (opt_input opts) diff --git a/src/Main.hs b/src/Main.hs index c875209..3dfa546 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -1,5 +1,7 @@ import Data.List (intercalate, intersperse) -import System.Exit (exitFailure) +import System.Exit (ExitCode(..), exitWith) +import System.IO (stderr, hPutStrLn) + import Text.Regex.Posix import Cidr (Cidr, @@ -13,6 +15,18 @@ import Cidr (Cidr, max_third_octet, max_fourth_octet) +import CommandLine (help_set, + help_text, + input_function, + parse_errors) + +-- Some exit codes, used in the ExitFailure constructor. +exit_invalid_cidr :: Int +exit_invalid_cidr = 1 + +exit_args_parse_failed :: Int +exit_args_parse_failed = 2 + -- A regular expression that matches a non-address character. non_addr_char :: String @@ -76,7 +90,7 @@ numeric_range x y = upper = maximum [x,y] --- Take a CIDR String, and exitFailure if it's invalid. +-- Take a CIDR String, and exit with a failure if it's invalid. validate_or_die :: String -> IO () validate_or_die cidr = do if (is_valid_cidr cidr) @@ -84,15 +98,36 @@ validate_or_die cidr = do return () else do putStrLn "Error: not valid CIDR notation." - exitFailure + exitWith (ExitFailure exit_invalid_cidr) + main :: IO () main = do - input <- getContents + -- First, check for any errors that occurred while parsing + -- the command line options. + errors <- CommandLine.parse_errors + if not (null errors) + then do + hPutStrLn stderr (concat errors) + putStrLn CommandLine.help_text + exitWith (ExitFailure exit_args_parse_failed) + else do -- Nothing + + -- Next, check to see if the 'help' option was passed to the + -- program. If it was, display the help, and exit successfully. + help_opt_set <- CommandLine.help_set + if help_opt_set + then do + putStrLn CommandLine.help_text + exitWith ExitSuccess + else do -- Nothing + + -- The input function we receive here should know what to read. + inputfunc <- (CommandLine.input_function) + input <- inputfunc let cidr_strings = lines input mapM validate_or_die cidr_strings let cidrs = map Cidr.from_string cidr_strings let regexes = map cidr_to_regex cidrs putStrLn $ alternate regexes - -- 2.43.2