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
52 import Data.Char (toUpper, isAlphaNum, isDigit, isSpace)
53 import Data.Function (on)
58 import Ganeti.BasicTypes
59 import qualified Ganeti.Constants as C
66 -- | To be used only for debugging, breaks referential integrity.
67 debug :: Show a => a -> a
68 debug x = trace (show x) x
70 -- | Displays a modified form of the second parameter before returning
72 debugFn :: Show b => (a -> b) -> a -> a
73 debugFn fn x = debug (fn x) `seq` x
75 -- | Show the first parameter before returning the second one.
76 debugXy :: Show a => a -> b -> b
81 -- | Apply the function if condition holds, otherwise use default value.
82 applyIf :: Bool -> (a -> a) -> a -> a
83 applyIf b f x = if b then f x else x
85 -- | Comma-join a string list.
86 commaJoin :: [String] -> String
87 commaJoin = intercalate ","
89 -- | Split a list on a separator and return an array.
90 sepSplit :: Eq a => a -> [a] -> [[a]]
95 | otherwise = x:sepSplit sep ys
96 where (x, xs) = break (== sep) s
99 -- | Simple pluralize helper
100 plural :: Int -> String -> String -> String
104 -- | Ensure a value is quoted if needed.
105 ensureQuoted :: String -> String
106 ensureQuoted v = if not (all (\c -> isAlphaNum c || c == '.') v)
110 -- * Mathematical functions
112 -- Simple and slow statistical functions, please replace with better
115 -- | Standard deviation function.
116 stdDev :: [Double] -> Double
118 -- first, calculate the list length and sum lst in a single step,
119 -- for performance reasons
120 let (ll', sx) = foldl' (\(rl, rs) e ->
123 in rl' `seq` rs' `seq` (rl', rs')) (0::Int, 0) lst
124 ll = fromIntegral ll'::Double
126 av = foldl' (\accu em -> let d = em - mv in accu + d * d) 0.0 lst
127 in sqrt (av / ll) -- stddev
129 -- * Logical functions
131 -- Avoid syntactic sugar and enhance readability. These functions are proposed
132 -- by some for inclusion in the Prelude, and at the moment they are present
133 -- (with various definitions) in the utility-ht package. Some rationale and
134 -- discussion is available at <http://www.haskell.org/haskellwiki/If-then-else>
136 -- | \"if\" as a function, rather than as syntactic sugar.
137 if' :: Bool -- ^ condition
138 -> a -- ^ \"then\" result
139 -> a -- ^ \"else\" result
140 -> a -- ^ \"then\" or "else" result depending on the condition
144 -- * Parsing utility functions
146 -- | Parse results from readsPrec.
147 parseChoices :: (Monad m, Read a) => String -> String -> [(a, String)] -> m a
148 parseChoices _ _ ((v, ""):[]) = return v
149 parseChoices name s ((_, e):[]) =
150 fail $ name ++ ": leftover characters when parsing '"
151 ++ s ++ "': '" ++ e ++ "'"
152 parseChoices name s _ = fail $ name ++ ": cannot parse string '" ++ s ++ "'"
154 -- | Safe 'read' function returning data encapsulated in a Result.
155 tryRead :: (Monad m, Read a) => String -> String -> m a
156 tryRead name s = parseChoices name s $ reads s
158 -- | Format a table of strings to maintain consistent length.
159 formatTable :: [[String]] -> [Bool] -> [[String]]
160 formatTable vals numpos =
161 let vtrans = transpose vals -- transpose, so that we work on rows
162 -- rather than columns
163 mlens = map (maximum . map length) vtrans
164 expnd = map (\(flds, isnum, ml) ->
166 let delta = ml - length val
167 filler = replicate delta ' '
174 ) (zip3 vtrans numpos mlens)
177 -- | Constructs a printable table from given header and rows
178 printTable :: String -> [String] -> [[String]] -> [Bool] -> String
179 printTable lp header rows isnum =
180 unlines . map ((++) lp . (:) ' ' . unwords) $
181 formatTable (header:rows) isnum
183 -- | Converts a unit (e.g. m or GB) into a scaling factor.
184 parseUnitValue :: (Monad m) => String -> m Rational
186 -- binary conversions first
187 | null unit = return 1
188 | unit == "m" || upper == "MIB" = return 1
189 | unit == "g" || upper == "GIB" = return kbBinary
190 | unit == "t" || upper == "TIB" = return $ kbBinary * kbBinary
192 | unit == "M" || upper == "MB" = return mbFactor
193 | unit == "G" || upper == "GB" = return $ mbFactor * kbDecimal
194 | unit == "T" || upper == "TB" = return $ mbFactor * kbDecimal * kbDecimal
195 | otherwise = fail $ "Unknown unit '" ++ unit ++ "'"
196 where upper = map toUpper unit
197 kbBinary = 1024 :: Rational
198 kbDecimal = 1000 :: Rational
199 decToBin = kbDecimal / kbBinary -- factor for 1K conversion
200 mbFactor = decToBin * decToBin -- twice the factor for just 1K
202 -- | Tries to extract number and scale from the given string.
204 -- Input must be in the format NUMBER+ SPACE* [UNIT]. If no unit is
205 -- specified, it defaults to MiB. Return value is always an integral
207 parseUnit :: (Monad m, Integral a, Read a) => String -> m a
209 -- TODO: enhance this by splitting the unit parsing code out and
210 -- accepting floating-point numbers
211 case (reads str::[(Int, String)]) of
213 let unit = dropWhile (== ' ') suffix
215 scaling <- parseUnitValue unit
216 return $ truncate (fromIntegral v * scaling)
217 _ -> fail $ "Can't parse string '" ++ str ++ "'"
219 -- | Unwraps a 'Result', exiting the program if it is a 'Bad' value,
220 -- otherwise returning the actual contained value.
221 exitIfBad :: String -> Result a -> IO a
222 exitIfBad msg (Bad s) = exitErr (msg ++ ": " ++ s)
223 exitIfBad _ (Ok v) = return v
225 -- | Exits immediately with an error message.
226 exitErr :: String -> IO a
228 hPutStrLn stderr $ "Error: " ++ errmsg
229 exitWith (ExitFailure 1)
231 -- | Exits with an error message if the given boolean condition if true.
232 exitWhen :: Bool -> String -> IO ()
233 exitWhen True msg = exitErr msg
234 exitWhen False _ = return ()
236 -- | Exits with an error message /unless/ the given boolean condition
237 -- if true, the opposite of 'exitWhen'.
238 exitUnless :: Bool -> String -> IO ()
239 exitUnless cond = exitWhen (not cond)
241 -- | Helper for 'niceSort'. Computes the key element for a given string.
242 extractKey :: [Either Integer String] -- ^ Current (partial) key, reversed
243 -> String -- ^ Remaining string
244 -> ([Either Integer String], String)
245 extractKey ek [] = (reverse ek, [])
246 extractKey ek xs@(x:_) =
247 let (span_fn, conv_fn) = if isDigit x
248 then (isDigit, Left . read)
249 else (not . isDigit, Right)
250 (k, rest) = span span_fn xs
251 in extractKey (conv_fn k:ek) rest
253 {-| Sort a list of strings based on digit and non-digit groupings.
255 Given a list of names @['a1', 'a10', 'a11', 'a2']@ this function
256 will sort the list in the logical order @['a1', 'a2', 'a10', 'a11']@.
258 The sort algorithm breaks each name in groups of either only-digits or
259 no-digits, and sorts based on each group.
261 Internally, this is not implemented via regexes (like the Python
262 version), but via actual splitting of the string in sequences of
263 either digits or everything else, and converting the digit sequences
264 in /Left Integer/ and the non-digit ones in /Right String/, at which
265 point sorting becomes trivial due to the built-in 'Either' ordering;
266 we only need one extra step of dropping the key at the end.
269 niceSort :: [String] -> [String]
270 niceSort = niceSortKey id
272 -- | Key-version of 'niceSort'. We use 'sortBy' and @compare `on` fst@
273 -- since we don't want to add an ordering constraint on the /a/ type,
274 -- hence the need to only compare the first element of the /(key, a)/
276 niceSortKey :: (a -> String) -> [a] -> [a]
278 map snd . sortBy (compare `on` fst) .
279 map (\s -> (fst . extractKey [] $ keyfn s, s))
281 -- | Strip space characthers (including newline). As this is
282 -- expensive, should only be run on small strings.
283 rStripSpace :: String -> String
284 rStripSpace = reverse . dropWhile isSpace . reverse
286 -- | Returns a random UUID.
287 -- This is a Linux-specific method as it uses the /proc filesystem.
290 contents <- readFile C.randomUuidFile
291 return $! rStripSpace $ take 128 contents
293 -- | Convert a ClockTime into a (seconds-only) timestamp.
294 clockTimeToString :: ClockTime -> String
295 clockTimeToString (TOD t _) = show t
297 {-| Strip a prefix from a string, allowing the last character of the prefix
298 (which is assumed to be a separator) to be absent from the string if the string
301 >>> chompPrefix "foo:bar:" "a:b:c"
304 >>> chompPrefix "foo:bar:" "foo:bar:baz"
307 >>> chompPrefix "foo:bar:" "foo:bar:"
310 >>> chompPrefix "foo:bar:" "foo:bar"
313 >>> chompPrefix "foo:bar:" "foo:barbaz"
316 chompPrefix :: String -> String -> Maybe String
317 chompPrefix pfx str =
318 if pfx `isPrefixOf` str || str == init pfx
319 then Just $ drop (length pfx) str