Add two confd library tests
[ganeti-local] / htools / Ganeti / BasicTypes.hs
1 {-
2
3 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 02110-1301, USA.
19
20 -}
21
22 module Ganeti.BasicTypes
23   ( Result(..)
24   , isOk
25   , isBad
26   , eitherToResult
27   , annotateResult
28   , annotateIOError
29   , select
30   , LookupResult(..)
31   , MatchPriority(..)
32   , lookupName
33   , goodLookupResult
34   , goodMatchPriority
35   , prefixMatch
36   , compareNameComponent
37   ) where
38
39 import Control.Monad
40 import Data.Function
41 import Data.List
42
43 -- | This is similar to the JSON library Result type - /very/ similar,
44 -- but we want to use it in multiple places, so we abstract it into a
45 -- mini-library here.
46 --
47 -- The failure value for this monad is simply a string.
48 data Result a
49     = Bad String
50     | Ok a
51     deriving (Show, Read, Eq)
52
53 instance Monad Result where
54   (>>=) (Bad x) _ = Bad x
55   (>>=) (Ok x) fn = fn x
56   return = Ok
57   fail = Bad
58
59 instance Functor Result where
60   fmap _ (Bad msg) = Bad msg
61   fmap fn (Ok val) = Ok (fn val)
62
63 instance MonadPlus Result where
64   mzero = Bad "zero Result when used as MonadPlus"
65   -- for mplus, when we 'add' two Bad values, we concatenate their
66   -- error descriptions
67   (Bad x) `mplus` (Bad y) = Bad (x ++ "; " ++ y)
68   (Bad _) `mplus` x = x
69   x@(Ok _) `mplus` _ = x
70
71 -- | Simple checker for whether a 'Result' is OK.
72 isOk :: Result a -> Bool
73 isOk (Ok _) = True
74 isOk _ = False
75
76 -- | Simple checker for whether a 'Result' is a failure.
77 isBad :: Result a  -> Bool
78 isBad = not . isOk
79
80 -- | Converter from Either String to 'Result'.
81 eitherToResult :: Either String a -> Result a
82 eitherToResult (Left s) = Bad s
83 eitherToResult (Right v) = Ok v
84
85 -- | Annotate a Result with an ownership information.
86 annotateResult :: String -> Result a -> Result a
87 annotateResult owner (Bad s) = Bad $ owner ++ ": " ++ s
88 annotateResult _ v = v
89
90 -- | Annotates and transforms IOErrors into a Result type. This can be
91 -- used in the error handler argument to 'catch', for example.
92 annotateIOError :: String -> IOError -> IO (Result a)
93 annotateIOError description exc =
94   return . Bad $ description ++ ": " ++ show exc
95
96 -- * Misc functionality
97
98 -- | Return the first result with a True condition, or the default otherwise.
99 select :: a            -- ^ default result
100        -> [(Bool, a)]  -- ^ list of \"condition, result\"
101        -> a            -- ^ first result which has a True condition, or default
102 select def = maybe def snd . find fst
103
104 -- * Lookup of partial names functionality
105
106 -- | The priority of a match in a lookup result.
107 data MatchPriority = ExactMatch
108                    | MultipleMatch
109                    | PartialMatch
110                    | FailMatch
111                    deriving (Show, Read, Enum, Eq, Ord)
112
113 -- | The result of a name lookup in a list.
114 data LookupResult = LookupResult
115   { lrMatchPriority :: MatchPriority -- ^ The result type
116   -- | Matching value (for ExactMatch, PartialMatch), Lookup string otherwise
117   , lrContent :: String
118   } deriving (Show, Read)
119
120 -- | Lookup results have an absolute preference ordering.
121 instance Eq LookupResult where
122   (==) = (==) `on` lrMatchPriority
123
124 instance Ord LookupResult where
125   compare = compare `on` lrMatchPriority
126
127 -- | Check for prefix matches in names.
128 -- Implemented in Ganeti core utils.text.MatchNameComponent
129 -- as the regexp r"^%s(\..*)?$" % re.escape(key)
130 prefixMatch :: String  -- ^ Lookup
131             -> String  -- ^ Full name
132             -> Bool    -- ^ Whether there is a prefix match
133 prefixMatch = isPrefixOf . (++ ".")
134
135 -- | Is the lookup priority a "good" one?
136 goodMatchPriority :: MatchPriority -> Bool
137 goodMatchPriority ExactMatch = True
138 goodMatchPriority PartialMatch = True
139 goodMatchPriority _ = False
140
141 -- | Is the lookup result an actual match?
142 goodLookupResult :: LookupResult -> Bool
143 goodLookupResult = goodMatchPriority . lrMatchPriority
144
145 -- | Compares a canonical name and a lookup string.
146 compareNameComponent :: String        -- ^ Canonical (target) name
147                      -> String        -- ^ Partial (lookup) name
148                      -> LookupResult  -- ^ Result of the lookup
149 compareNameComponent cnl lkp =
150   select (LookupResult FailMatch lkp)
151   [ (cnl == lkp          , LookupResult ExactMatch cnl)
152   , (prefixMatch lkp cnl , LookupResult PartialMatch cnl)
153   ]
154
155 -- | Lookup a string and choose the best result.
156 chooseLookupResult :: String       -- ^ Lookup key
157                    -> String       -- ^ String to compare to the lookup key
158                    -> LookupResult -- ^ Previous result
159                    -> LookupResult -- ^ New result
160 chooseLookupResult lkp cstr old =
161   -- default: use class order to pick the minimum result
162   select (min new old)
163   -- special cases:
164   -- short circuit if the new result is an exact match
165   [ (lrMatchPriority new == ExactMatch, new)
166   -- if both are partial matches generate a multiple match
167   , (partial2, LookupResult MultipleMatch lkp)
168   ] where new = compareNameComponent cstr lkp
169           partial2 = all ((PartialMatch==) . lrMatchPriority) [old, new]
170
171 -- | Find the canonical name for a lookup string in a list of names.
172 lookupName :: [String]      -- ^ List of keys
173            -> String        -- ^ Lookup string
174            -> LookupResult  -- ^ Result of the lookup
175 lookupName l s = foldr (chooseLookupResult s)
176                        (LookupResult FailMatch s) l