Revision da9e2aff

b/src/Ganeti/Utils.hs
53 53
  , trim
54 54
  , defaultHead
55 55
  , exitIfEmpty
56
  , splitEithers
57
  , recombineEithers
56 58
  ) where
57 59

  
58 60
import Data.Char (toUpper, isAlphaNum, isDigit, isSpace)
59 61
import Data.Function (on)
60 62
import Data.List
63
import Control.Monad (foldM)
61 64

  
62 65
import Debug.Trace
63 66

  
......
382 385
exitIfEmpty :: String -> [a] -> IO a
383 386
exitIfEmpty _ (x:_) = return x
384 387
exitIfEmpty s []    = exitErr s
388

  
389
-- | Split an 'Either' list into two separate lists (containing the
390
-- 'Left' and 'Right' elements, plus a \"trail\" list that allows
391
-- recombination later.
392
--
393
-- This is splitter; for recombination, look at 'recombineEithers'.
394
-- The sum of \"left\" and \"right\" lists should be equal to the
395
-- original list length, and the trail list should be the same length
396
-- as well. The entries in the resulting lists are reversed in
397
-- comparison with the original list.
398
splitEithers :: [Either a b] -> ([a], [b], [Bool])
399
splitEithers = foldl' splitter ([], [], [])
400
  where splitter (l, r, t) e =
401
          case e of
402
            Left  v -> (v:l, r, False:t)
403
            Right v -> (l, v:r, True:t)
404

  
405
-- | Recombines two \"left\" and \"right\" lists using a \"trail\"
406
-- list into a single 'Either' list.
407
--
408
-- This is the counterpart to 'splitEithers'. It does the opposite
409
-- transformation, and the output list will be the reverse of the
410
-- input lists. Since 'splitEithers' also reverses the lists, calling
411
-- these together will result in the original list.
412
--
413
-- Mismatches in the structure of the lists (e.g. inconsistent
414
-- lengths) are represented via 'Bad'; normally this function should
415
-- not fail, if lists are passed as generated by 'splitEithers'.
416
recombineEithers :: (Show a, Show b) =>
417
                    [a] -> [b] -> [Bool] -> Result [Either a b]
418
recombineEithers lefts rights trail =
419
  foldM recombiner ([], lefts, rights) trail >>= checker
420
    where checker (eithers, [], []) = Ok eithers
421
          checker (_, lefts', rights') =
422
            Bad $ "Inconsistent results after recombination, l'=" ++
423
                show lefts' ++ ", r'=" ++ show rights'
424
          recombiner (es, l:ls, rs) False = Ok (Left l:es,  ls, rs)
425
          recombiner (es, ls, r:rs) True  = Ok (Right r:es, ls, rs)
426
          recombiner (_,  ls, rs) t = Bad $ "Inconsistent trail log: l=" ++
427
                                      show ls ++ ", r=" ++ show rs ++ ",t=" ++
428
                                      show t
b/test/hs/Test/Ganeti/Utils.hs
32 32
import Test.HUnit
33 33

  
34 34
import Data.Char (isSpace)
35
import qualified Data.Either as Either
35 36
import Data.List
36 37
import System.Time
37 38
import qualified Text.JSON as J
......
294 295
              trim "" ==? ""
295 296
          ]
296 297

  
298
-- | Tests 'splitEithers' and 'recombineEithers'.
299
prop_splitRecombineEithers :: [Either Int Int] -> Property
300
prop_splitRecombineEithers es =
301
  conjoin
302
  [ printTestCase "only lefts are mapped correctly" $
303
    splitEithers (map Left lefts) ==? (reverse lefts, emptylist, falses)
304
  , printTestCase "only rights are mapped correctly" $
305
    splitEithers (map Right rights) ==? (emptylist, reverse rights, trues)
306
  , printTestCase "recombination is no-op" $
307
    recombineEithers splitleft splitright trail ==? Ok es
308
  , printTestCase "fail on too long lefts" $
309
    isBad (recombineEithers (0:splitleft) splitright trail)
310
  , printTestCase "fail on too long rights" $
311
    isBad (recombineEithers splitleft (0:splitright) trail)
312
  , printTestCase "fail on too long trail" $
313
    isBad (recombineEithers splitleft splitright (True:trail))
314
  ]
315
  where (lefts, rights) = Either.partitionEithers es
316
        falses = map (const False) lefts
317
        trues = map (const True) rights
318
        (splitleft, splitright, trail) = splitEithers es
319
        emptylist = []::[Int]
320

  
297 321
-- | Test list for the Utils module.
298 322
testSuite "Utils"
299 323
            [ 'prop_commaJoinSplit
......
319 343
            , 'prop_chompPrefix_last
320 344
            , 'prop_chompPrefix_empty_string
321 345
            , 'prop_chompPrefix_nothing
346
            , 'prop_splitRecombineEithers
322 347
            ]

Also available in: Unified diff