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