Merge branch 'devel-2.6' into submit
[ganeti-local] / htools / Ganeti / Utils.hs
index 708755e..9636a34 100644 (file)
@@ -37,13 +37,16 @@ module Ganeti.Utils
   , printTable
   , parseUnit
   , plural
+  , niceSort
+  , niceSortKey
   , exitIfBad
   , exitErr
   , exitWhen
   , exitUnless
   ) where
 
-import Data.Char (toUpper, isAlphaNum)
+import Data.Char (toUpper, isAlphaNum, isDigit)
+import Data.Function (on)
 import Data.List
 
 import Debug.Trace
@@ -210,15 +213,13 @@ parseUnit str =
 -- | Unwraps a 'Result', exiting the program if it is a 'Bad' value,
 -- otherwise returning the actual contained value.
 exitIfBad :: String -> Result a -> IO a
-exitIfBad msg (Bad s) = do
-  hPutStrLn stderr $ "Error: " ++ msg ++ ": " ++ s
-  exitWith (ExitFailure 1)
+exitIfBad msg (Bad s) = exitErr (msg ++ ": " ++ s)
 exitIfBad _ (Ok v) = return v
 
 -- | Exits immediately with an error message.
 exitErr :: String -> IO a
 exitErr errmsg = do
-  hPutStrLn stderr $ "Error: " ++ errmsg ++ "."
+  hPutStrLn stderr $ "Error: " ++ errmsg
   exitWith (ExitFailure 1)
 
 -- | Exits with an error message if the given boolean condition if true.
@@ -230,3 +231,43 @@ exitWhen False _  = return ()
 -- if true, the opposite of 'exitWhen'.
 exitUnless :: Bool -> String -> IO ()
 exitUnless cond = exitWhen (not cond)
+
+-- | Helper for 'niceSort'. Computes the key element for a given string.
+extractKey :: [Either Integer String]  -- ^ Current (partial) key, reversed
+           -> String                   -- ^ Remaining string
+           -> ([Either Integer String], String)
+extractKey ek [] = (reverse ek, [])
+extractKey ek xs@(x:_) =
+  let (span_fn, conv_fn) = if isDigit x
+                             then (isDigit, Left . read)
+                             else (not . isDigit, Right)
+      (k, rest) = span span_fn xs
+  in extractKey (conv_fn k:ek) rest
+
+{-| Sort a list of strings based on digit and non-digit groupings.
+
+Given a list of names @['a1', 'a10', 'a11', 'a2']@ this function
+will sort the list in the logical order @['a1', 'a2', 'a10', 'a11']@.
+
+The sort algorithm breaks each name in groups of either only-digits or
+no-digits, and sorts based on each group.
+
+Internally, this is not implemented via regexes (like the Python
+version), but via actual splitting of the string in sequences of
+either digits or everything else, and converting the digit sequences
+in /Left Integer/ and the non-digit ones in /Right String/, at which
+point sorting becomes trivial due to the built-in 'Either' ordering;
+we only need one extra step of dropping the key at the end.
+
+-}
+niceSort :: [String] -> [String]
+niceSort = map snd . sort . map (\s -> (fst $ extractKey [] s, s))
+
+-- | Key-version of 'niceSort'. We use 'sortBy' and @compare `on` fst@
+-- since we don't want to add an ordering constraint on the /a/ type,
+-- hence the need to only compare the first element of the /(key, a)/
+-- tuple.
+niceSortKey :: (a -> String) -> [a] -> [a]
+niceSortKey keyfn =
+  map snd . sortBy (compare `on` fst) .
+  map (\s -> (fst . extractKey [] $ keyfn s, s))