Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / Utils.hs @ 986a8671

History | View | Annotate | Download (15.4 kB)

1 525bfb36 Iustin Pop
{-| Utility functions. -}
2 e4f08c46 Iustin Pop
3 e2fa2baf Iustin Pop
{-
4 e2fa2baf Iustin Pop
5 a7f0953a Iustin Pop
Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
6 e2fa2baf Iustin Pop
7 e2fa2baf Iustin Pop
This program is free software; you can redistribute it and/or modify
8 e2fa2baf Iustin Pop
it under the terms of the GNU General Public License as published by
9 e2fa2baf Iustin Pop
the Free Software Foundation; either version 2 of the License, or
10 e2fa2baf Iustin Pop
(at your option) any later version.
11 e2fa2baf Iustin Pop
12 e2fa2baf Iustin Pop
This program is distributed in the hope that it will be useful, but
13 e2fa2baf Iustin Pop
WITHOUT ANY WARRANTY; without even the implied warranty of
14 e2fa2baf Iustin Pop
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 e2fa2baf Iustin Pop
General Public License for more details.
16 e2fa2baf Iustin Pop
17 e2fa2baf Iustin Pop
You should have received a copy of the GNU General Public License
18 e2fa2baf Iustin Pop
along with this program; if not, write to the Free Software
19 e2fa2baf Iustin Pop
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 e2fa2baf Iustin Pop
02110-1301, USA.
21 e2fa2baf Iustin Pop
22 e2fa2baf Iustin Pop
-}
23 e2fa2baf Iustin Pop
24 26d62e4c Iustin Pop
module Ganeti.Utils
25 ebf38064 Iustin Pop
  ( debug
26 ebf38064 Iustin Pop
  , debugFn
27 ebf38064 Iustin Pop
  , debugXy
28 ebf38064 Iustin Pop
  , sepSplit
29 ebf38064 Iustin Pop
  , stdDev
30 ebf38064 Iustin Pop
  , if'
31 ebf38064 Iustin Pop
  , select
32 ebf38064 Iustin Pop
  , applyIf
33 ebf38064 Iustin Pop
  , commaJoin
34 79eef90b Agata Murawska
  , ensureQuoted
35 ebf38064 Iustin Pop
  , tryRead
36 ebf38064 Iustin Pop
  , formatTable
37 c3024b7e René Nussbaumer
  , printTable
38 ebf38064 Iustin Pop
  , parseUnit
39 19e310cc René Nussbaumer
  , plural
40 04edfc99 Iustin Pop
  , niceSort
41 04edfc99 Iustin Pop
  , niceSortKey
42 88a10df5 Iustin Pop
  , exitIfBad
43 88a10df5 Iustin Pop
  , exitErr
44 88a10df5 Iustin Pop
  , exitWhen
45 88a10df5 Iustin Pop
  , exitUnless
46 256e28c4 Iustin Pop
  , rStripSpace
47 80a0546b Michele Tartara
  , newUUID
48 ace37e24 Michele Tartara
  , getCurrentTime
49 a6e054a8 Iustin Pop
  , getCurrentTimeUSec
50 b6aeda4a Dato Simó
  , clockTimeToString
51 b009f682 Dato Simó
  , chompPrefix
52 7dbe4c72 Klaus Aehlig
  , warn
53 9fb621af Yiannis Tsiouris
  , wrap
54 9fb621af Yiannis Tsiouris
  , trim
55 72747d91 Iustin Pop
  , defaultHead
56 72747d91 Iustin Pop
  , exitIfEmpty
57 da9e2aff Iustin Pop
  , splitEithers
58 da9e2aff Iustin Pop
  , recombineEithers
59 986a8671 Michele Tartara
  , resolveAddr
60 ebf38064 Iustin Pop
  ) where
61 e4f08c46 Iustin Pop
62 256e28c4 Iustin Pop
import Data.Char (toUpper, isAlphaNum, isDigit, isSpace)
63 04edfc99 Iustin Pop
import Data.Function (on)
64 29ac5975 Iustin Pop
import Data.List
65 da9e2aff Iustin Pop
import Control.Monad (foldM)
66 e4f08c46 Iustin Pop
67 e4f08c46 Iustin Pop
import Debug.Trace
68 986a8671 Michele Tartara
import Network.Socket
69 e4f08c46 Iustin Pop
70 88a10df5 Iustin Pop
import Ganeti.BasicTypes
71 80a0546b Michele Tartara
import qualified Ganeti.Constants as C
72 88a10df5 Iustin Pop
import System.IO
73 88a10df5 Iustin Pop
import System.Exit
74 b6aeda4a Dato Simó
import System.Time
75 88a10df5 Iustin Pop
76 9188aeef Iustin Pop
-- * Debug functions
77 9188aeef Iustin Pop
78 e4f08c46 Iustin Pop
-- | To be used only for debugging, breaks referential integrity.
79 e4f08c46 Iustin Pop
debug :: Show a => a -> a
80 e4f08c46 Iustin Pop
debug x = trace (show x) x
81 e4f08c46 Iustin Pop
82 525bfb36 Iustin Pop
-- | Displays a modified form of the second parameter before returning
83 525bfb36 Iustin Pop
-- it.
84 adc5c176 Iustin Pop
debugFn :: Show b => (a -> b) -> a -> a
85 adc5c176 Iustin Pop
debugFn fn x = debug (fn x) `seq` x
86 adc5c176 Iustin Pop
87 525bfb36 Iustin Pop
-- | Show the first parameter before returning the second one.
88 adc5c176 Iustin Pop
debugXy :: Show a => a -> b -> b
89 05ff7a00 Agata Murawska
debugXy = seq . debug
90 adc5c176 Iustin Pop
91 525bfb36 Iustin Pop
-- * Miscellaneous
92 1b7cf8ca Iustin Pop
93 61bbbed7 Agata Murawska
-- | Apply the function if condition holds, otherwise use default value.
94 61bbbed7 Agata Murawska
applyIf :: Bool -> (a -> a) -> a -> a
95 61bbbed7 Agata Murawska
applyIf b f x = if b then f x else x
96 61bbbed7 Agata Murawska
97 e4f08c46 Iustin Pop
-- | Comma-join a string list.
98 e4f08c46 Iustin Pop
commaJoin :: [String] -> String
99 e4f08c46 Iustin Pop
commaJoin = intercalate ","
100 e4f08c46 Iustin Pop
101 748d5d50 Iustin Pop
-- | Split a list on a separator and return an array.
102 748d5d50 Iustin Pop
sepSplit :: Eq a => a -> [a] -> [[a]]
103 e4f08c46 Iustin Pop
sepSplit sep s
104 ebf38064 Iustin Pop
  | null s    = []
105 ebf38064 Iustin Pop
  | null xs   = [x]
106 ebf38064 Iustin Pop
  | null ys   = [x,[]]
107 ebf38064 Iustin Pop
  | otherwise = x:sepSplit sep ys
108 ebf38064 Iustin Pop
  where (x, xs) = break (== sep) s
109 ebf38064 Iustin Pop
        ys = drop 1 xs
110 e4f08c46 Iustin Pop
111 19e310cc René Nussbaumer
-- | Simple pluralize helper
112 19e310cc René Nussbaumer
plural :: Int -> String -> String -> String
113 19e310cc René Nussbaumer
plural 1 s _ = s
114 19e310cc René Nussbaumer
plural _ _ p = p
115 19e310cc René Nussbaumer
116 79eef90b Agata Murawska
-- | Ensure a value is quoted if needed.
117 79eef90b Agata Murawska
ensureQuoted :: String -> String
118 79eef90b Agata Murawska
ensureQuoted v = if not (all (\c -> isAlphaNum c || c == '.') v)
119 79eef90b Agata Murawska
                 then '\'':v ++ "'"
120 79eef90b Agata Murawska
                 else v
121 79eef90b Agata Murawska
122 9188aeef Iustin Pop
-- * Mathematical functions
123 9188aeef Iustin Pop
124 185297fa Iustin Pop
-- Simple and slow statistical functions, please replace with better
125 185297fa Iustin Pop
-- versions
126 e4f08c46 Iustin Pop
127 525bfb36 Iustin Pop
-- | Standard deviation function.
128 4715711d Iustin Pop
stdDev :: [Double] -> Double
129 4715711d Iustin Pop
stdDev lst =
130 7570569e Iustin Pop
  -- first, calculate the list length and sum lst in a single step,
131 7570569e Iustin Pop
  -- for performance reasons
132 7570569e Iustin Pop
  let (ll', sx) = foldl' (\(rl, rs) e ->
133 7570569e Iustin Pop
                           let rl' = rl + 1
134 7570569e Iustin Pop
                               rs' = rs + e
135 7570569e Iustin Pop
                           in rl' `seq` rs' `seq` (rl', rs')) (0::Int, 0) lst
136 7570569e Iustin Pop
      ll = fromIntegral ll'::Double
137 7570569e Iustin Pop
      mv = sx / ll
138 7570569e Iustin Pop
      av = foldl' (\accu em -> let d = em - mv in accu + d * d) 0.0 lst
139 4715711d Iustin Pop
  in sqrt (av / ll) -- stddev
140 dd4c56ed Iustin Pop
141 bfe6c954 Guido Trotter
-- *  Logical functions
142 bfe6c954 Guido Trotter
143 bfe6c954 Guido Trotter
-- Avoid syntactic sugar and enhance readability. These functions are proposed
144 bfe6c954 Guido Trotter
-- by some for inclusion in the Prelude, and at the moment they are present
145 bfe6c954 Guido Trotter
-- (with various definitions) in the utility-ht package. Some rationale and
146 bfe6c954 Guido Trotter
-- discussion is available at <http://www.haskell.org/haskellwiki/If-then-else>
147 bfe6c954 Guido Trotter
148 bfe6c954 Guido Trotter
-- | \"if\" as a function, rather than as syntactic sugar.
149 bfe6c954 Guido Trotter
if' :: Bool -- ^ condition
150 bfe6c954 Guido Trotter
    -> a    -- ^ \"then\" result
151 bfe6c954 Guido Trotter
    -> a    -- ^ \"else\" result
152 bfe6c954 Guido Trotter
    -> a    -- ^ \"then\" or "else" result depending on the condition
153 bfe6c954 Guido Trotter
if' True x _ = x
154 bfe6c954 Guido Trotter
if' _    _ y = y
155 bfe6c954 Guido Trotter
156 5b763470 Iustin Pop
-- * Parsing utility functions
157 5b763470 Iustin Pop
158 525bfb36 Iustin Pop
-- | Parse results from readsPrec.
159 5b763470 Iustin Pop
parseChoices :: (Monad m, Read a) => String -> String -> [(a, String)] -> m a
160 5b763470 Iustin Pop
parseChoices _ _ ((v, ""):[]) = return v
161 5b763470 Iustin Pop
parseChoices name s ((_, e):[]) =
162 5b763470 Iustin Pop
    fail $ name ++ ": leftover characters when parsing '"
163 5b763470 Iustin Pop
           ++ s ++ "': '" ++ e ++ "'"
164 5b763470 Iustin Pop
parseChoices name s _ = fail $ name ++ ": cannot parse string '" ++ s ++ "'"
165 5b763470 Iustin Pop
166 5b763470 Iustin Pop
-- | Safe 'read' function returning data encapsulated in a Result.
167 5b763470 Iustin Pop
tryRead :: (Monad m, Read a) => String -> String -> m a
168 5b763470 Iustin Pop
tryRead name s = parseChoices name s $ reads s
169 c5f7412e Iustin Pop
170 525bfb36 Iustin Pop
-- | Format a table of strings to maintain consistent length.
171 c5f7412e Iustin Pop
formatTable :: [[String]] -> [Bool] -> [[String]]
172 c5f7412e Iustin Pop
formatTable vals numpos =
173 c5f7412e Iustin Pop
    let vtrans = transpose vals  -- transpose, so that we work on rows
174 c5f7412e Iustin Pop
                                 -- rather than columns
175 c5f7412e Iustin Pop
        mlens = map (maximum . map length) vtrans
176 c5f7412e Iustin Pop
        expnd = map (\(flds, isnum, ml) ->
177 c5f7412e Iustin Pop
                         map (\val ->
178 c5f7412e Iustin Pop
                                  let delta = ml - length val
179 c5f7412e Iustin Pop
                                      filler = replicate delta ' '
180 c5f7412e Iustin Pop
                                  in if delta > 0
181 c5f7412e Iustin Pop
                                     then if isnum
182 c5f7412e Iustin Pop
                                          then filler ++ val
183 c5f7412e Iustin Pop
                                          else val ++ filler
184 c5f7412e Iustin Pop
                                     else val
185 c5f7412e Iustin Pop
                             ) flds
186 c5f7412e Iustin Pop
                    ) (zip3 vtrans numpos mlens)
187 c5f7412e Iustin Pop
   in transpose expnd
188 9b9da389 Iustin Pop
189 c3024b7e René Nussbaumer
-- | Constructs a printable table from given header and rows
190 c3024b7e René Nussbaumer
printTable :: String -> [String] -> [[String]] -> [Bool] -> String
191 c3024b7e René Nussbaumer
printTable lp header rows isnum =
192 2cdaf225 Iustin Pop
  unlines . map ((++) lp . (:) ' ' . unwords) $
193 c3024b7e René Nussbaumer
  formatTable (header:rows) isnum
194 c3024b7e René Nussbaumer
195 1cdcf8f3 Iustin Pop
-- | Converts a unit (e.g. m or GB) into a scaling factor.
196 1cdcf8f3 Iustin Pop
parseUnitValue :: (Monad m) => String -> m Rational
197 1cdcf8f3 Iustin Pop
parseUnitValue unit
198 1cdcf8f3 Iustin Pop
  -- binary conversions first
199 1cdcf8f3 Iustin Pop
  | null unit                     = return 1
200 1cdcf8f3 Iustin Pop
  | unit == "m" || upper == "MIB" = return 1
201 1cdcf8f3 Iustin Pop
  | unit == "g" || upper == "GIB" = return kbBinary
202 1cdcf8f3 Iustin Pop
  | unit == "t" || upper == "TIB" = return $ kbBinary * kbBinary
203 1cdcf8f3 Iustin Pop
  -- SI conversions
204 1cdcf8f3 Iustin Pop
  | unit == "M" || upper == "MB"  = return mbFactor
205 1cdcf8f3 Iustin Pop
  | unit == "G" || upper == "GB"  = return $ mbFactor * kbDecimal
206 1cdcf8f3 Iustin Pop
  | unit == "T" || upper == "TB"  = return $ mbFactor * kbDecimal * kbDecimal
207 1cdcf8f3 Iustin Pop
  | otherwise = fail $ "Unknown unit '" ++ unit ++ "'"
208 1cdcf8f3 Iustin Pop
  where upper = map toUpper unit
209 5850e990 Iustin Pop
        kbBinary = 1024 :: Rational
210 5850e990 Iustin Pop
        kbDecimal = 1000 :: Rational
211 1cdcf8f3 Iustin Pop
        decToBin = kbDecimal / kbBinary -- factor for 1K conversion
212 1cdcf8f3 Iustin Pop
        mbFactor = decToBin * decToBin -- twice the factor for just 1K
213 1cdcf8f3 Iustin Pop
214 1cb92fac Iustin Pop
-- | Tries to extract number and scale from the given string.
215 1cb92fac Iustin Pop
--
216 1cb92fac Iustin Pop
-- Input must be in the format NUMBER+ SPACE* [UNIT]. If no unit is
217 1cb92fac Iustin Pop
-- specified, it defaults to MiB. Return value is always an integral
218 1cb92fac Iustin Pop
-- value in MiB.
219 1cb92fac Iustin Pop
parseUnit :: (Monad m, Integral a, Read a) => String -> m a
220 1cb92fac Iustin Pop
parseUnit str =
221 ebf38064 Iustin Pop
  -- TODO: enhance this by splitting the unit parsing code out and
222 ebf38064 Iustin Pop
  -- accepting floating-point numbers
223 1cdcf8f3 Iustin Pop
  case (reads str::[(Int, String)]) of
224 ebf38064 Iustin Pop
    [(v, suffix)] ->
225 ebf38064 Iustin Pop
      let unit = dropWhile (== ' ') suffix
226 1cdcf8f3 Iustin Pop
      in do
227 1cdcf8f3 Iustin Pop
        scaling <- parseUnitValue unit
228 1cdcf8f3 Iustin Pop
        return $ truncate (fromIntegral v * scaling)
229 ebf38064 Iustin Pop
    _ -> fail $ "Can't parse string '" ++ str ++ "'"
230 88a10df5 Iustin Pop
231 88a10df5 Iustin Pop
-- | Unwraps a 'Result', exiting the program if it is a 'Bad' value,
232 88a10df5 Iustin Pop
-- otherwise returning the actual contained value.
233 88a10df5 Iustin Pop
exitIfBad :: String -> Result a -> IO a
234 707cd3d7 Helga Velroyen
exitIfBad msg (Bad s) = exitErr (msg ++ ": " ++ s)
235 88a10df5 Iustin Pop
exitIfBad _ (Ok v) = return v
236 88a10df5 Iustin Pop
237 88a10df5 Iustin Pop
-- | Exits immediately with an error message.
238 88a10df5 Iustin Pop
exitErr :: String -> IO a
239 88a10df5 Iustin Pop
exitErr errmsg = do
240 707cd3d7 Helga Velroyen
  hPutStrLn stderr $ "Error: " ++ errmsg
241 88a10df5 Iustin Pop
  exitWith (ExitFailure 1)
242 88a10df5 Iustin Pop
243 88a10df5 Iustin Pop
-- | Exits with an error message if the given boolean condition if true.
244 88a10df5 Iustin Pop
exitWhen :: Bool -> String -> IO ()
245 88a10df5 Iustin Pop
exitWhen True msg = exitErr msg
246 88a10df5 Iustin Pop
exitWhen False _  = return ()
247 88a10df5 Iustin Pop
248 88a10df5 Iustin Pop
-- | Exits with an error message /unless/ the given boolean condition
249 88a10df5 Iustin Pop
-- if true, the opposite of 'exitWhen'.
250 88a10df5 Iustin Pop
exitUnless :: Bool -> String -> IO ()
251 88a10df5 Iustin Pop
exitUnless cond = exitWhen (not cond)
252 04edfc99 Iustin Pop
253 7dbe4c72 Klaus Aehlig
-- | Print a warning, but do not exit.
254 7dbe4c72 Klaus Aehlig
warn :: String -> IO ()
255 7dbe4c72 Klaus Aehlig
warn = hPutStrLn stderr . (++) "Warning: "
256 7dbe4c72 Klaus Aehlig
257 04edfc99 Iustin Pop
-- | Helper for 'niceSort'. Computes the key element for a given string.
258 04edfc99 Iustin Pop
extractKey :: [Either Integer String]  -- ^ Current (partial) key, reversed
259 04edfc99 Iustin Pop
           -> String                   -- ^ Remaining string
260 04edfc99 Iustin Pop
           -> ([Either Integer String], String)
261 04edfc99 Iustin Pop
extractKey ek [] = (reverse ek, [])
262 04edfc99 Iustin Pop
extractKey ek xs@(x:_) =
263 04edfc99 Iustin Pop
  let (span_fn, conv_fn) = if isDigit x
264 04edfc99 Iustin Pop
                             then (isDigit, Left . read)
265 04edfc99 Iustin Pop
                             else (not . isDigit, Right)
266 04edfc99 Iustin Pop
      (k, rest) = span span_fn xs
267 04edfc99 Iustin Pop
  in extractKey (conv_fn k:ek) rest
268 04edfc99 Iustin Pop
269 04edfc99 Iustin Pop
{-| Sort a list of strings based on digit and non-digit groupings.
270 04edfc99 Iustin Pop
271 04edfc99 Iustin Pop
Given a list of names @['a1', 'a10', 'a11', 'a2']@ this function
272 04edfc99 Iustin Pop
will sort the list in the logical order @['a1', 'a2', 'a10', 'a11']@.
273 04edfc99 Iustin Pop
274 04edfc99 Iustin Pop
The sort algorithm breaks each name in groups of either only-digits or
275 04edfc99 Iustin Pop
no-digits, and sorts based on each group.
276 04edfc99 Iustin Pop
277 04edfc99 Iustin Pop
Internally, this is not implemented via regexes (like the Python
278 04edfc99 Iustin Pop
version), but via actual splitting of the string in sequences of
279 04edfc99 Iustin Pop
either digits or everything else, and converting the digit sequences
280 04edfc99 Iustin Pop
in /Left Integer/ and the non-digit ones in /Right String/, at which
281 04edfc99 Iustin Pop
point sorting becomes trivial due to the built-in 'Either' ordering;
282 04edfc99 Iustin Pop
we only need one extra step of dropping the key at the end.
283 04edfc99 Iustin Pop
284 04edfc99 Iustin Pop
-}
285 04edfc99 Iustin Pop
niceSort :: [String] -> [String]
286 a7f0953a Iustin Pop
niceSort = niceSortKey id
287 04edfc99 Iustin Pop
288 04edfc99 Iustin Pop
-- | Key-version of 'niceSort'. We use 'sortBy' and @compare `on` fst@
289 04edfc99 Iustin Pop
-- since we don't want to add an ordering constraint on the /a/ type,
290 04edfc99 Iustin Pop
-- hence the need to only compare the first element of the /(key, a)/
291 04edfc99 Iustin Pop
-- tuple.
292 04edfc99 Iustin Pop
niceSortKey :: (a -> String) -> [a] -> [a]
293 04edfc99 Iustin Pop
niceSortKey keyfn =
294 04edfc99 Iustin Pop
  map snd . sortBy (compare `on` fst) .
295 04edfc99 Iustin Pop
  map (\s -> (fst . extractKey [] $ keyfn s, s))
296 256e28c4 Iustin Pop
297 256e28c4 Iustin Pop
-- | Strip space characthers (including newline). As this is
298 256e28c4 Iustin Pop
-- expensive, should only be run on small strings.
299 256e28c4 Iustin Pop
rStripSpace :: String -> String
300 256e28c4 Iustin Pop
rStripSpace = reverse . dropWhile isSpace . reverse
301 80a0546b Michele Tartara
302 80a0546b Michele Tartara
-- | Returns a random UUID.
303 80a0546b Michele Tartara
-- This is a Linux-specific method as it uses the /proc filesystem.
304 80a0546b Michele Tartara
newUUID :: IO String
305 80a0546b Michele Tartara
newUUID = do
306 80a0546b Michele Tartara
  contents <- readFile C.randomUuidFile
307 37dfcacb Iustin Pop
  return $! rStripSpace $ take 128 contents
308 b6aeda4a Dato Simó
309 a6e054a8 Iustin Pop
-- | Returns the current time as an 'Integer' representing the number
310 a6e054a8 Iustin Pop
-- of seconds from the Unix epoch.
311 ace37e24 Michele Tartara
getCurrentTime :: IO Integer
312 ace37e24 Michele Tartara
getCurrentTime = do
313 ace37e24 Michele Tartara
  TOD ctime _ <- getClockTime
314 ace37e24 Michele Tartara
  return ctime
315 ace37e24 Michele Tartara
316 a6e054a8 Iustin Pop
-- | Returns the current time as an 'Integer' representing the number
317 a6e054a8 Iustin Pop
-- of microseconds from the Unix epoch (hence the need for 'Integer').
318 a6e054a8 Iustin Pop
getCurrentTimeUSec :: IO Integer
319 a6e054a8 Iustin Pop
getCurrentTimeUSec = do
320 a6e054a8 Iustin Pop
  TOD ctime pico <- getClockTime
321 a6e054a8 Iustin Pop
  -- pico: 10^-12, micro: 10^-6, so we have to shift seconds left and
322 a6e054a8 Iustin Pop
  -- picoseconds right
323 a6e054a8 Iustin Pop
  return $ ctime * 1000000 + pico `div` 1000000
324 a6e054a8 Iustin Pop
325 b6aeda4a Dato Simó
-- | Convert a ClockTime into a (seconds-only) timestamp.
326 b6aeda4a Dato Simó
clockTimeToString :: ClockTime -> String
327 b6aeda4a Dato Simó
clockTimeToString (TOD t _) = show t
328 b009f682 Dato Simó
329 b009f682 Dato Simó
{-| Strip a prefix from a string, allowing the last character of the prefix
330 b009f682 Dato Simó
(which is assumed to be a separator) to be absent from the string if the string
331 b009f682 Dato Simó
terminates there.
332 b009f682 Dato Simó
333 94042ae4 Michele Tartara
\>>> chompPrefix \"foo:bar:\" \"a:b:c\"
334 b009f682 Dato Simó
Nothing
335 b009f682 Dato Simó
336 94042ae4 Michele Tartara
\>>> chompPrefix \"foo:bar:\" \"foo:bar:baz\"
337 94042ae4 Michele Tartara
Just \"baz\"
338 b009f682 Dato Simó
339 94042ae4 Michele Tartara
\>>> chompPrefix \"foo:bar:\" \"foo:bar:\"
340 94042ae4 Michele Tartara
Just \"\"
341 b009f682 Dato Simó
342 94042ae4 Michele Tartara
\>>> chompPrefix \"foo:bar:\" \"foo:bar\"
343 94042ae4 Michele Tartara
Just \"\"
344 b009f682 Dato Simó
345 94042ae4 Michele Tartara
\>>> chompPrefix \"foo:bar:\" \"foo:barbaz\"
346 b009f682 Dato Simó
Nothing
347 b009f682 Dato Simó
-}
348 b009f682 Dato Simó
chompPrefix :: String -> String -> Maybe String
349 b009f682 Dato Simó
chompPrefix pfx str =
350 b009f682 Dato Simó
  if pfx `isPrefixOf` str || str == init pfx
351 b009f682 Dato Simó
    then Just $ drop (length pfx) str
352 b009f682 Dato Simó
    else Nothing
353 9fb621af Yiannis Tsiouris
354 9fb621af Yiannis Tsiouris
-- | Breaks a string in lines with length \<= maxWidth.
355 9fb621af Yiannis Tsiouris
--
356 9fb621af Yiannis Tsiouris
-- NOTE: The split is OK if:
357 9fb621af Yiannis Tsiouris
--
358 9fb621af Yiannis Tsiouris
-- * It doesn't break a word, i.e. the next line begins with space
359 9fb621af Yiannis Tsiouris
--   (@isSpace . head $ rest@) or the current line ends with space
360 9fb621af Yiannis Tsiouris
--   (@null revExtra@);
361 9fb621af Yiannis Tsiouris
--
362 9fb621af Yiannis Tsiouris
-- * It breaks a very big word that doesn't fit anyway (@null revLine@).
363 9fb621af Yiannis Tsiouris
wrap :: Int      -- ^ maxWidth
364 9fb621af Yiannis Tsiouris
     -> String   -- ^ string that needs wrapping
365 9fb621af Yiannis Tsiouris
     -> [String] -- ^ string \"broken\" in lines
366 9fb621af Yiannis Tsiouris
wrap maxWidth = filter (not . null) . map trim . wrap0
367 9fb621af Yiannis Tsiouris
  where wrap0 :: String -> [String]
368 9fb621af Yiannis Tsiouris
        wrap0 text
369 9fb621af Yiannis Tsiouris
          | length text <= maxWidth = [text]
370 9fb621af Yiannis Tsiouris
          | isSplitOK               = line : wrap0 rest
371 9fb621af Yiannis Tsiouris
          | otherwise               = line' : wrap0 rest'
372 9fb621af Yiannis Tsiouris
          where (line, rest) = splitAt maxWidth text
373 9fb621af Yiannis Tsiouris
                (revExtra, revLine) = break isSpace . reverse $ line
374 9fb621af Yiannis Tsiouris
                (line', rest') = (reverse revLine, reverse revExtra ++ rest)
375 9fb621af Yiannis Tsiouris
                isSplitOK =
376 9fb621af Yiannis Tsiouris
                  null revLine || null revExtra || startsWithSpace rest
377 9fb621af Yiannis Tsiouris
                startsWithSpace (x:_) = isSpace x
378 9fb621af Yiannis Tsiouris
                startsWithSpace _     = False
379 9fb621af Yiannis Tsiouris
380 9fb621af Yiannis Tsiouris
-- | Removes surrounding whitespace. Should only be used in small
381 9fb621af Yiannis Tsiouris
-- strings.
382 9fb621af Yiannis Tsiouris
trim :: String -> String
383 9fb621af Yiannis Tsiouris
trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace
384 72747d91 Iustin Pop
385 72747d91 Iustin Pop
-- | A safer head version, with a default value.
386 72747d91 Iustin Pop
defaultHead :: a -> [a] -> a
387 72747d91 Iustin Pop
defaultHead def []    = def
388 72747d91 Iustin Pop
defaultHead _   (x:_) = x
389 72747d91 Iustin Pop
390 72747d91 Iustin Pop
-- | A 'head' version in the I/O monad, for validating parameters
391 72747d91 Iustin Pop
-- without which we cannot continue.
392 72747d91 Iustin Pop
exitIfEmpty :: String -> [a] -> IO a
393 72747d91 Iustin Pop
exitIfEmpty _ (x:_) = return x
394 72747d91 Iustin Pop
exitIfEmpty s []    = exitErr s
395 da9e2aff Iustin Pop
396 da9e2aff Iustin Pop
-- | Split an 'Either' list into two separate lists (containing the
397 da9e2aff Iustin Pop
-- 'Left' and 'Right' elements, plus a \"trail\" list that allows
398 da9e2aff Iustin Pop
-- recombination later.
399 da9e2aff Iustin Pop
--
400 da9e2aff Iustin Pop
-- This is splitter; for recombination, look at 'recombineEithers'.
401 da9e2aff Iustin Pop
-- The sum of \"left\" and \"right\" lists should be equal to the
402 da9e2aff Iustin Pop
-- original list length, and the trail list should be the same length
403 da9e2aff Iustin Pop
-- as well. The entries in the resulting lists are reversed in
404 da9e2aff Iustin Pop
-- comparison with the original list.
405 da9e2aff Iustin Pop
splitEithers :: [Either a b] -> ([a], [b], [Bool])
406 da9e2aff Iustin Pop
splitEithers = foldl' splitter ([], [], [])
407 da9e2aff Iustin Pop
  where splitter (l, r, t) e =
408 da9e2aff Iustin Pop
          case e of
409 da9e2aff Iustin Pop
            Left  v -> (v:l, r, False:t)
410 da9e2aff Iustin Pop
            Right v -> (l, v:r, True:t)
411 da9e2aff Iustin Pop
412 da9e2aff Iustin Pop
-- | Recombines two \"left\" and \"right\" lists using a \"trail\"
413 da9e2aff Iustin Pop
-- list into a single 'Either' list.
414 da9e2aff Iustin Pop
--
415 da9e2aff Iustin Pop
-- This is the counterpart to 'splitEithers'. It does the opposite
416 da9e2aff Iustin Pop
-- transformation, and the output list will be the reverse of the
417 da9e2aff Iustin Pop
-- input lists. Since 'splitEithers' also reverses the lists, calling
418 da9e2aff Iustin Pop
-- these together will result in the original list.
419 da9e2aff Iustin Pop
--
420 da9e2aff Iustin Pop
-- Mismatches in the structure of the lists (e.g. inconsistent
421 da9e2aff Iustin Pop
-- lengths) are represented via 'Bad'; normally this function should
422 da9e2aff Iustin Pop
-- not fail, if lists are passed as generated by 'splitEithers'.
423 da9e2aff Iustin Pop
recombineEithers :: (Show a, Show b) =>
424 da9e2aff Iustin Pop
                    [a] -> [b] -> [Bool] -> Result [Either a b]
425 da9e2aff Iustin Pop
recombineEithers lefts rights trail =
426 da9e2aff Iustin Pop
  foldM recombiner ([], lefts, rights) trail >>= checker
427 da9e2aff Iustin Pop
    where checker (eithers, [], []) = Ok eithers
428 da9e2aff Iustin Pop
          checker (_, lefts', rights') =
429 da9e2aff Iustin Pop
            Bad $ "Inconsistent results after recombination, l'=" ++
430 da9e2aff Iustin Pop
                show lefts' ++ ", r'=" ++ show rights'
431 da9e2aff Iustin Pop
          recombiner (es, l:ls, rs) False = Ok (Left l:es,  ls, rs)
432 da9e2aff Iustin Pop
          recombiner (es, ls, r:rs) True  = Ok (Right r:es, ls, rs)
433 da9e2aff Iustin Pop
          recombiner (_,  ls, rs) t = Bad $ "Inconsistent trail log: l=" ++
434 da9e2aff Iustin Pop
                                      show ls ++ ", r=" ++ show rs ++ ",t=" ++
435 da9e2aff Iustin Pop
                                      show t
436 986a8671 Michele Tartara
437 986a8671 Michele Tartara
-- | Default hints for the resolver
438 986a8671 Michele Tartara
resolveAddrHints :: Maybe AddrInfo
439 986a8671 Michele Tartara
resolveAddrHints =
440 986a8671 Michele Tartara
  Just defaultHints { addrFlags = [AI_NUMERICHOST, AI_NUMERICSERV] }
441 986a8671 Michele Tartara
442 986a8671 Michele Tartara
-- | Resolves a numeric address.
443 986a8671 Michele Tartara
resolveAddr :: Int -> String -> IO (Result (Family, SockAddr))
444 986a8671 Michele Tartara
resolveAddr port str = do
445 986a8671 Michele Tartara
  resolved <- getAddrInfo resolveAddrHints (Just str) (Just (show port))
446 986a8671 Michele Tartara
  return $ case resolved of
447 986a8671 Michele Tartara
             [] -> Bad "Invalid results from lookup?"
448 986a8671 Michele Tartara
             best:_ -> Ok (addrFamily best, addrAddress best)