4 import Control.Monad (when)
5 import Data.List ((\\), intercalate)
6 import qualified Data.List as List (sort)
7 import Data.Maybe (catMaybes, isNothing)
8 import System.Exit (ExitCode( ExitFailure ), exitWith)
9 import System.IO (stderr, hPutStrLn)
10 import Text.Read (readMaybe)
24 import qualified Cidr ( normalize )
26 Args( Regexed, Reduced, Duped, Diffed, Listed, barriers, normalize, sort ),
28 import ExitCodes ( exit_invalid_cidr )
32 -- | A regular expression that matches a non-address character.
34 non_addr_char :: String
35 non_addr_char = "[^\\.0-9]"
38 -- | Add non_addr_chars on either side of the given String. This
39 -- prevents (for example) the regex '127.0.0.1' from matching
42 add_barriers :: String -> String
43 add_barriers x = non_addr_char ++ x ++ non_addr_char
46 -- | The magic happens here. We take a CIDR String as an argument, and
47 -- return the equivalent regular expression. We do this as follows:
49 -- 1. Compute the minimum possible value of each octet.
50 -- 2. Compute the maximum possible value of each octet.
51 -- 3. Generate a regex matching every value between those min and
53 -- 4. Join the regexes from step 3 with regexes matching periods.
54 -- 5. Stick an address boundary on either side of the result if
55 -- use_barriers is True.
57 cidr_to_regex :: Bool -> Cidr -> String
58 cidr_to_regex use_barriers cidr =
59 let f = if use_barriers then add_barriers else id in
60 f (intercalate "\\." [range1, range2, range3, range4])
62 range1 = numeric_range min1 max1
63 range2 = numeric_range min2 max2
64 range3 = numeric_range min3 max3
65 range4 = numeric_range min4 max4
66 min1 = fromEnum (min_octet1 cidr)
67 min2 = fromEnum (min_octet2 cidr)
68 min3 = fromEnum (min_octet3 cidr)
69 min4 = fromEnum (min_octet4 cidr)
70 max1 = fromEnum (max_octet1 cidr)
71 max2 = fromEnum (max_octet2 cidr)
72 max3 = fromEnum (max_octet3 cidr)
73 max4 = fromEnum (max_octet4 cidr)
77 -- | Take a list of Strings, and return a regular expression matching
80 alternate :: [String] -> String
81 alternate terms = "(" ++ (intercalate "|" terms) ++ ")"
84 -- | Take two Ints as parameters, and return a regex matching any
85 -- integer between them (inclusive).
87 -- IMPORTANT: we match from max to min so that if e.g. the last
88 -- octet is '255', we want '255' to match before '2' in the regex
89 -- (255|254|...|3|2|1) which does not happen if we use
90 -- (1|2|3|...|254|255).
92 numeric_range :: Int -> Int -> String
94 alternate (map show $ reverse [lower..upper])
107 let cidr_strings = words input
108 let cidrs = map readMaybe cidr_strings :: [Maybe Cidr]
110 when (any isNothing cidrs) $ do
111 hPutStrLn stderr "ERROR: not valid CIDR notation:"
113 -- Output the bad lines, safely.
114 let pairs = zip cidr_strings cidrs
116 let print_pair :: (String, Maybe Cidr) -> IO ()
117 print_pair (x, Nothing) = hPutStrLn stderr (" * " ++ x)
118 print_pair (_, _) = return ()
120 mapM_ print_pair pairs
121 exitWith (ExitFailure exit_invalid_cidr)
123 -- Filter out only the valid ones.
124 let valid_cidrs = catMaybes cidrs
128 let cidrs' = combine_all valid_cidrs
129 let regexes = map (cidr_to_regex (barriers args)) cidrs'
130 putStrLn $ alternate regexes
132 -- Pre-normalize all CIDRs if the user asked for it.
133 let nrml_func = if (normalize args) then Cidr.normalize else id
134 let sort_func = if (sort args) then List.sort else id :: [Cidr] -> [Cidr]
135 mapM_ (print . nrml_func) (sort_func $ combine_all valid_cidrs)
139 dupes = valid_cidrs \\ (combine_all valid_cidrs)
141 mapM_ putStrLn deletions
142 mapM_ putStrLn additions
144 dupes = valid_cidrs \\ (combine_all valid_cidrs)
145 deletions = map (\s -> '-' : (show s)) dupes
146 newcidrs = (combine_all valid_cidrs) \\ valid_cidrs
147 additions = map (\s -> '+' : (show s)) newcidrs
149 let combined_cidrs = combine_all valid_cidrs
150 let addrs = concatMap enumerate combined_cidrs