1 {-| Utility functions. -}
5 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
56 import Data.Char (toUpper, isAlphaNum, isDigit, isSpace)
57 import Data.Function (on)
62 import Ganeti.BasicTypes
63 import qualified Ganeti.Constants as C
70 -- | To be used only for debugging, breaks referential integrity.
71 debug :: Show a => a -> a
72 debug x = trace (show x) x
74 -- | Displays a modified form of the second parameter before returning
76 debugFn :: Show b => (a -> b) -> a -> a
77 debugFn fn x = debug (fn x) `seq` x
79 -- | Show the first parameter before returning the second one.
80 debugXy :: Show a => a -> b -> b
85 -- | Apply the function if condition holds, otherwise use default value.
86 applyIf :: Bool -> (a -> a) -> a -> a
87 applyIf b f x = if b then f x else x
89 -- | Comma-join a string list.
90 commaJoin :: [String] -> String
91 commaJoin = intercalate ","
93 -- | Split a list on a separator and return an array.
94 sepSplit :: Eq a => a -> [a] -> [[a]]
99 | otherwise = x:sepSplit sep ys
100 where (x, xs) = break (== sep) s
103 -- | Simple pluralize helper
104 plural :: Int -> String -> String -> String
108 -- | Ensure a value is quoted if needed.
109 ensureQuoted :: String -> String
110 ensureQuoted v = if not (all (\c -> isAlphaNum c || c == '.') v)
114 -- * Mathematical functions
116 -- Simple and slow statistical functions, please replace with better
119 -- | Standard deviation function.
120 stdDev :: [Double] -> Double
122 -- first, calculate the list length and sum lst in a single step,
123 -- for performance reasons
124 let (ll', sx) = foldl' (\(rl, rs) e ->
127 in rl' `seq` rs' `seq` (rl', rs')) (0::Int, 0) lst
128 ll = fromIntegral ll'::Double
130 av = foldl' (\accu em -> let d = em - mv in accu + d * d) 0.0 lst
131 in sqrt (av / ll) -- stddev
133 -- * Logical functions
135 -- Avoid syntactic sugar and enhance readability. These functions are proposed
136 -- by some for inclusion in the Prelude, and at the moment they are present
137 -- (with various definitions) in the utility-ht package. Some rationale and
138 -- discussion is available at <http://www.haskell.org/haskellwiki/If-then-else>
140 -- | \"if\" as a function, rather than as syntactic sugar.
141 if' :: Bool -- ^ condition
142 -> a -- ^ \"then\" result
143 -> a -- ^ \"else\" result
144 -> a -- ^ \"then\" or "else" result depending on the condition
148 -- * Parsing utility functions
150 -- | Parse results from readsPrec.
151 parseChoices :: (Monad m, Read a) => String -> String -> [(a, String)] -> m a
152 parseChoices _ _ ((v, ""):[]) = return v
153 parseChoices name s ((_, e):[]) =
154 fail $ name ++ ": leftover characters when parsing '"
155 ++ s ++ "': '" ++ e ++ "'"
156 parseChoices name s _ = fail $ name ++ ": cannot parse string '" ++ s ++ "'"
158 -- | Safe 'read' function returning data encapsulated in a Result.
159 tryRead :: (Monad m, Read a) => String -> String -> m a
160 tryRead name s = parseChoices name s $ reads s
162 -- | Format a table of strings to maintain consistent length.
163 formatTable :: [[String]] -> [Bool] -> [[String]]
164 formatTable vals numpos =
165 let vtrans = transpose vals -- transpose, so that we work on rows
166 -- rather than columns
167 mlens = map (maximum . map length) vtrans
168 expnd = map (\(flds, isnum, ml) ->
170 let delta = ml - length val
171 filler = replicate delta ' '
178 ) (zip3 vtrans numpos mlens)
181 -- | Constructs a printable table from given header and rows
182 printTable :: String -> [String] -> [[String]] -> [Bool] -> String
183 printTable lp header rows isnum =
184 unlines . map ((++) lp . (:) ' ' . unwords) $
185 formatTable (header:rows) isnum
187 -- | Converts a unit (e.g. m or GB) into a scaling factor.
188 parseUnitValue :: (Monad m) => String -> m Rational
190 -- binary conversions first
191 | null unit = return 1
192 | unit == "m" || upper == "MIB" = return 1
193 | unit == "g" || upper == "GIB" = return kbBinary
194 | unit == "t" || upper == "TIB" = return $ kbBinary * kbBinary
196 | unit == "M" || upper == "MB" = return mbFactor
197 | unit == "G" || upper == "GB" = return $ mbFactor * kbDecimal
198 | unit == "T" || upper == "TB" = return $ mbFactor * kbDecimal * kbDecimal
199 | otherwise = fail $ "Unknown unit '" ++ unit ++ "'"
200 where upper = map toUpper unit
201 kbBinary = 1024 :: Rational
202 kbDecimal = 1000 :: Rational
203 decToBin = kbDecimal / kbBinary -- factor for 1K conversion
204 mbFactor = decToBin * decToBin -- twice the factor for just 1K
206 -- | Tries to extract number and scale from the given string.
208 -- Input must be in the format NUMBER+ SPACE* [UNIT]. If no unit is
209 -- specified, it defaults to MiB. Return value is always an integral
211 parseUnit :: (Monad m, Integral a, Read a) => String -> m a
213 -- TODO: enhance this by splitting the unit parsing code out and
214 -- accepting floating-point numbers
215 case (reads str::[(Int, String)]) of
217 let unit = dropWhile (== ' ') suffix
219 scaling <- parseUnitValue unit
220 return $ truncate (fromIntegral v * scaling)
221 _ -> fail $ "Can't parse string '" ++ str ++ "'"
223 -- | Unwraps a 'Result', exiting the program if it is a 'Bad' value,
224 -- otherwise returning the actual contained value.
225 exitIfBad :: String -> Result a -> IO a
226 exitIfBad msg (Bad s) = exitErr (msg ++ ": " ++ s)
227 exitIfBad _ (Ok v) = return v
229 -- | Exits immediately with an error message.
230 exitErr :: String -> IO a
232 hPutStrLn stderr $ "Error: " ++ errmsg
233 exitWith (ExitFailure 1)
235 -- | Exits with an error message if the given boolean condition if true.
236 exitWhen :: Bool -> String -> IO ()
237 exitWhen True msg = exitErr msg
238 exitWhen False _ = return ()
240 -- | Exits with an error message /unless/ the given boolean condition
241 -- if true, the opposite of 'exitWhen'.
242 exitUnless :: Bool -> String -> IO ()
243 exitUnless cond = exitWhen (not cond)
245 -- | Helper for 'niceSort'. Computes the key element for a given string.
246 extractKey :: [Either Integer String] -- ^ Current (partial) key, reversed
247 -> String -- ^ Remaining string
248 -> ([Either Integer String], String)
249 extractKey ek [] = (reverse ek, [])
250 extractKey ek xs@(x:_) =
251 let (span_fn, conv_fn) = if isDigit x
252 then (isDigit, Left . read)
253 else (not . isDigit, Right)
254 (k, rest) = span span_fn xs
255 in extractKey (conv_fn k:ek) rest
257 {-| Sort a list of strings based on digit and non-digit groupings.
259 Given a list of names @['a1', 'a10', 'a11', 'a2']@ this function
260 will sort the list in the logical order @['a1', 'a2', 'a10', 'a11']@.
262 The sort algorithm breaks each name in groups of either only-digits or
263 no-digits, and sorts based on each group.
265 Internally, this is not implemented via regexes (like the Python
266 version), but via actual splitting of the string in sequences of
267 either digits or everything else, and converting the digit sequences
268 in /Left Integer/ and the non-digit ones in /Right String/, at which
269 point sorting becomes trivial due to the built-in 'Either' ordering;
270 we only need one extra step of dropping the key at the end.
273 niceSort :: [String] -> [String]
274 niceSort = niceSortKey id
276 -- | Key-version of 'niceSort'. We use 'sortBy' and @compare `on` fst@
277 -- since we don't want to add an ordering constraint on the /a/ type,
278 -- hence the need to only compare the first element of the /(key, a)/
280 niceSortKey :: (a -> String) -> [a] -> [a]
282 map snd . sortBy (compare `on` fst) .
283 map (\s -> (fst . extractKey [] $ keyfn s, s))
285 -- | Strip space characthers (including newline). As this is
286 -- expensive, should only be run on small strings.
287 rStripSpace :: String -> String
288 rStripSpace = reverse . dropWhile isSpace . reverse
290 -- | Returns a random UUID.
291 -- This is a Linux-specific method as it uses the /proc filesystem.
294 contents <- readFile C.randomUuidFile
295 return $! rStripSpace $ take 128 contents
297 -- | Returns the current time as an 'Integer' representing the number
298 -- of seconds from the Unix epoch.
299 getCurrentTime :: IO Integer
301 TOD ctime _ <- getClockTime
304 -- | Returns the current time as an 'Integer' representing the number
305 -- of microseconds from the Unix epoch (hence the need for 'Integer').
306 getCurrentTimeUSec :: IO Integer
307 getCurrentTimeUSec = do
308 TOD ctime pico <- getClockTime
309 -- pico: 10^-12, micro: 10^-6, so we have to shift seconds left and
311 return $ ctime * 1000000 + pico `div` 1000000
313 -- | Convert a ClockTime into a (seconds-only) timestamp.
314 clockTimeToString :: ClockTime -> String
315 clockTimeToString (TOD t _) = show t
317 {-| Strip a prefix from a string, allowing the last character of the prefix
318 (which is assumed to be a separator) to be absent from the string if the string
321 >>> chompPrefix "foo:bar:" "a:b:c"
324 >>> chompPrefix "foo:bar:" "foo:bar:baz"
327 >>> chompPrefix "foo:bar:" "foo:bar:"
330 >>> chompPrefix "foo:bar:" "foo:bar"
333 >>> chompPrefix "foo:bar:" "foo:barbaz"
336 chompPrefix :: String -> String -> Maybe String
337 chompPrefix pfx str =
338 if pfx `isPrefixOf` str || str == init pfx
339 then Just $ drop (length pfx) str
342 -- | Breaks a string in lines with length \<= maxWidth.
344 -- NOTE: The split is OK if:
346 -- * It doesn't break a word, i.e. the next line begins with space
347 -- (@isSpace . head $ rest@) or the current line ends with space
348 -- (@null revExtra@);
350 -- * It breaks a very big word that doesn't fit anyway (@null revLine@).
351 wrap :: Int -- ^ maxWidth
352 -> String -- ^ string that needs wrapping
353 -> [String] -- ^ string \"broken\" in lines
354 wrap maxWidth = filter (not . null) . map trim . wrap0
355 where wrap0 :: String -> [String]
357 | length text <= maxWidth = [text]
358 | isSplitOK = line : wrap0 rest
359 | otherwise = line' : wrap0 rest'
360 where (line, rest) = splitAt maxWidth text
361 (revExtra, revLine) = break isSpace . reverse $ line
362 (line', rest') = (reverse revLine, reverse revExtra ++ rest)
364 null revLine || null revExtra || startsWithSpace rest
365 startsWithSpace (x:_) = isSpace x
366 startsWithSpace _ = False
368 -- | Removes surrounding whitespace. Should only be used in small
370 trim :: String -> String
371 trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace