3 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
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.
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.
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
22 module Ganeti.BasicTypes
38 , compareNameComponent
41 import Control.Applicative
43 import Control.Monad.Trans
47 -- | This is similar to the JSON library Result type - /very/ similar,
48 -- but we want to use it in multiple places, so we abstract it into a
51 -- The failure value for this monad is simply a string.
55 deriving (Show, Read, Eq)
57 instance Monad Result where
58 (>>=) (Bad x) _ = Bad x
59 (>>=) (Ok x) fn = fn x
63 instance Functor Result where
64 fmap _ (Bad msg) = Bad msg
65 fmap fn (Ok val) = Ok (fn val)
67 instance MonadPlus Result where
68 mzero = Bad "zero Result when used as MonadPlus"
69 -- for mplus, when we 'add' two Bad values, we concatenate their
71 (Bad x) `mplus` (Bad y) = Bad (x ++ "; " ++ y)
73 x@(Ok _) `mplus` _ = x
75 instance Applicative Result where
79 (Ok f) <*> (Ok x) = Ok $ f x
81 -- | This is a monad transformation for Result. It's implementation is
82 -- based on the implementations of MaybeT and ErrorT.
83 newtype ResultT m a = ResultT {runResultT :: m (Result a)}
85 instance (Monad m) => Monad (ResultT m) where
86 fail err = ResultT (return $ Bad err)
87 return = lift . return
88 x >>= f = ResultT $ do
91 Ok val -> runResultT $ f val
92 Bad err -> return $ Bad err
94 instance MonadTrans ResultT where
95 lift x = ResultT (liftM Ok x)
97 instance (MonadIO m) => MonadIO (ResultT m) where
98 liftIO = lift . liftIO
100 -- | Lift a `Result` value to a `ResultT`.
101 resultT :: Monad m => Result a -> ResultT m a
102 resultT = ResultT . return
104 -- | Simple checker for whether a 'Result' is OK.
105 isOk :: Result a -> Bool
109 -- | Simple checker for whether a 'Result' is a failure.
110 isBad :: Result a -> Bool
113 -- | Converter from Either String to 'Result'.
114 eitherToResult :: Either String a -> Result a
115 eitherToResult (Left s) = Bad s
116 eitherToResult (Right v) = Ok v
118 -- | Annotate a Result with an ownership information.
119 annotateResult :: String -> Result a -> Result a
120 annotateResult owner (Bad s) = Bad $ owner ++ ": " ++ s
121 annotateResult _ v = v
123 -- | Annotates and transforms IOErrors into a Result type. This can be
124 -- used in the error handler argument to 'catch', for example.
125 annotateIOError :: String -> IOError -> IO (Result a)
126 annotateIOError description exc =
127 return . Bad $ description ++ ": " ++ show exc
129 -- * Misc functionality
131 -- | Return the first result with a True condition, or the default otherwise.
132 select :: a -- ^ default result
133 -> [(Bool, a)] -- ^ list of \"condition, result\"
134 -> a -- ^ first result which has a True condition, or default
135 select def = maybe def snd . find fst
137 -- * Lookup of partial names functionality
139 -- | The priority of a match in a lookup result.
140 data MatchPriority = ExactMatch
144 deriving (Show, Read, Enum, Eq, Ord)
146 -- | The result of a name lookup in a list.
147 data LookupResult = LookupResult
148 { lrMatchPriority :: MatchPriority -- ^ The result type
149 -- | Matching value (for ExactMatch, PartialMatch), Lookup string otherwise
150 , lrContent :: String
151 } deriving (Show, Read)
153 -- | Lookup results have an absolute preference ordering.
154 instance Eq LookupResult where
155 (==) = (==) `on` lrMatchPriority
157 instance Ord LookupResult where
158 compare = compare `on` lrMatchPriority
160 -- | Check for prefix matches in names.
161 -- Implemented in Ganeti core utils.text.MatchNameComponent
162 -- as the regexp r"^%s(\..*)?$" % re.escape(key)
163 prefixMatch :: String -- ^ Lookup
164 -> String -- ^ Full name
165 -> Bool -- ^ Whether there is a prefix match
166 prefixMatch = isPrefixOf . (++ ".")
168 -- | Is the lookup priority a "good" one?
169 goodMatchPriority :: MatchPriority -> Bool
170 goodMatchPriority ExactMatch = True
171 goodMatchPriority PartialMatch = True
172 goodMatchPriority _ = False
174 -- | Is the lookup result an actual match?
175 goodLookupResult :: LookupResult -> Bool
176 goodLookupResult = goodMatchPriority . lrMatchPriority
178 -- | Compares a canonical name and a lookup string.
179 compareNameComponent :: String -- ^ Canonical (target) name
180 -> String -- ^ Partial (lookup) name
181 -> LookupResult -- ^ Result of the lookup
182 compareNameComponent cnl lkp =
183 select (LookupResult FailMatch lkp)
184 [ (cnl == lkp , LookupResult ExactMatch cnl)
185 , (prefixMatch lkp cnl , LookupResult PartialMatch cnl)
188 -- | Lookup a string and choose the best result.
189 chooseLookupResult :: String -- ^ Lookup key
190 -> String -- ^ String to compare to the lookup key
191 -> LookupResult -- ^ Previous result
192 -> LookupResult -- ^ New result
193 chooseLookupResult lkp cstr old =
194 -- default: use class order to pick the minimum result
197 -- short circuit if the new result is an exact match
198 [ (lrMatchPriority new == ExactMatch, new)
199 -- if both are partial matches generate a multiple match
200 , (partial2, LookupResult MultipleMatch lkp)
201 ] where new = compareNameComponent cstr lkp
202 partial2 = all ((PartialMatch==) . lrMatchPriority) [old, new]
204 -- | Find the canonical name for a lookup string in a list of names.
205 lookupName :: [String] -- ^ List of keys
206 -> String -- ^ Lookup string
207 -> LookupResult -- ^ Result of the lookup
208 lookupName l s = foldr (chooseLookupResult s)
209 (LookupResult FailMatch s) l