Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / BasicTypes.hs @ b74ebe44

History | View | Annotate | Download (7 kB)

1
{-# LANGUAGE FlexibleInstances #-}
2

    
3
{-
4

    
5
Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
6

    
7
This program is free software; you can redistribute it and/or modify
8
it under the terms of the GNU General Public License as published by
9
the Free Software Foundation; either version 2 of the License, or
10
(at your option) any later version.
11

    
12
This program is distributed in the hope that it will be useful, but
13
WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
General Public License for more details.
16

    
17
You should have received a copy of the GNU General Public License
18
along with this program; if not, write to the Free Software
19
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20
02110-1301, USA.
21

    
22
-}
23

    
24
module Ganeti.BasicTypes
25
  ( GenericResult(..)
26
  , genericResult
27
  , Result
28
  , ResultT(..)
29
  , resultT
30
  , FromString(..)
31
  , isOk
32
  , isBad
33
  , eitherToResult
34
  , annotateResult
35
  , select
36
  , LookupResult(..)
37
  , MatchPriority(..)
38
  , lookupName
39
  , goodLookupResult
40
  , goodMatchPriority
41
  , prefixMatch
42
  , compareNameComponent
43
  ) where
44

    
45
import Control.Applicative
46
import Control.Monad
47
import Control.Monad.Trans
48
import Data.Function
49
import Data.List
50

    
51
-- | Generic monad for our error handling mechanisms.
52
data GenericResult a b
53
  = Bad a
54
  | Ok b
55
    deriving (Show, Eq)
56

    
57
-- | Sum type structure of GenericResult.
58
genericResult :: (a -> c) -> (b -> c) -> GenericResult a b -> c
59
genericResult f _ (Bad a) = f a
60
genericResult _ g (Ok b) = g b
61

    
62
-- | Type alias for a string Result.
63
type Result = GenericResult String
64

    
65
-- | Type class for things that can be built from strings.
66
class FromString a where
67
  mkFromString :: String -> a
68

    
69
-- | Trivial 'String' instance; requires FlexibleInstances extension
70
-- though.
71
instance FromString [Char] where
72
  mkFromString = id
73

    
74
-- | 'Monad' instance for 'GenericResult'.
75
instance (FromString a) => Monad (GenericResult a) where
76
  (>>=) (Bad x) _ = Bad x
77
  (>>=) (Ok x) fn = fn x
78
  return = Ok
79
  fail   = Bad . mkFromString
80

    
81
instance Functor (GenericResult a) where
82
  fmap _ (Bad msg) = Bad msg
83
  fmap fn (Ok val) = Ok (fn val)
84

    
85
instance MonadPlus (GenericResult String) where
86
  mzero = Bad "zero Result when used as MonadPlus"
87
  -- for mplus, when we 'add' two Bad values, we concatenate their
88
  -- error descriptions
89
  (Bad x) `mplus` (Bad y) = Bad (x ++ "; " ++ y)
90
  (Bad _) `mplus` x = x
91
  x@(Ok _) `mplus` _ = x
92

    
93
instance Applicative (GenericResult a) where
94
  pure = Ok
95
  (Bad f) <*> _       = Bad f
96
  _       <*> (Bad x) = Bad x
97
  (Ok f)  <*> (Ok x)  = Ok $ f x
98

    
99
-- | This is a monad transformation for Result. It's implementation is
100
-- based on the implementations of MaybeT and ErrorT.
101
newtype ResultT a m b = ResultT {runResultT :: m (GenericResult a b)}
102

    
103
instance (Monad m, FromString a) => Monad (ResultT a m) where
104
  fail err = ResultT (return . Bad $ mkFromString err)
105
  return   = lift . return
106
  x >>= f  = ResultT $ do
107
               a <- runResultT x
108
               case a of
109
                 Ok val -> runResultT $ f val
110
                 Bad err -> return $ Bad err
111

    
112
instance MonadTrans (ResultT a) where
113
  lift x = ResultT (liftM Ok x)
114

    
115
instance (MonadIO m, FromString a) => MonadIO (ResultT a m) where
116
  liftIO = lift . liftIO
117

    
118
-- | Lift a `Result` value to a `ResultT`.
119
resultT :: Monad m => GenericResult a b -> ResultT a m b
120
resultT = ResultT . return
121

    
122
-- | Simple checker for whether a 'GenericResult' is OK.
123
isOk :: GenericResult a b -> Bool
124
isOk (Ok _) = True
125
isOk _      = False
126

    
127
-- | Simple checker for whether a 'GenericResult' is a failure.
128
isBad :: GenericResult a b -> Bool
129
isBad = not . isOk
130

    
131
-- | Converter from Either to 'GenericResult'.
132
eitherToResult :: Either a b -> GenericResult a b
133
eitherToResult (Left  s) = Bad s
134
eitherToResult (Right v) = Ok  v
135

    
136
-- | Annotate a Result with an ownership information.
137
annotateResult :: String -> Result a -> Result a
138
annotateResult owner (Bad s) = Bad $ owner ++ ": " ++ s
139
annotateResult _ v = v
140

    
141
-- * Misc functionality
142

    
143
-- | Return the first result with a True condition, or the default otherwise.
144
select :: a            -- ^ default result
145
       -> [(Bool, a)]  -- ^ list of \"condition, result\"
146
       -> a            -- ^ first result which has a True condition, or default
147
select def = maybe def snd . find fst
148

    
149
-- * Lookup of partial names functionality
150

    
151
-- | The priority of a match in a lookup result.
152
data MatchPriority = ExactMatch
153
                   | MultipleMatch
154
                   | PartialMatch
155
                   | FailMatch
156
                   deriving (Show, Enum, Eq, Ord)
157

    
158
-- | The result of a name lookup in a list.
159
data LookupResult = LookupResult
160
  { lrMatchPriority :: MatchPriority -- ^ The result type
161
  -- | Matching value (for ExactMatch, PartialMatch), Lookup string otherwise
162
  , lrContent :: String
163
  } deriving (Show)
164

    
165
-- | Lookup results have an absolute preference ordering.
166
instance Eq LookupResult where
167
  (==) = (==) `on` lrMatchPriority
168

    
169
instance Ord LookupResult where
170
  compare = compare `on` lrMatchPriority
171

    
172
-- | Check for prefix matches in names.
173
-- Implemented in Ganeti core utils.text.MatchNameComponent
174
-- as the regexp r"^%s(\..*)?$" % re.escape(key)
175
prefixMatch :: String  -- ^ Lookup
176
            -> String  -- ^ Full name
177
            -> Bool    -- ^ Whether there is a prefix match
178
prefixMatch = isPrefixOf . (++ ".")
179

    
180
-- | Is the lookup priority a "good" one?
181
goodMatchPriority :: MatchPriority -> Bool
182
goodMatchPriority ExactMatch = True
183
goodMatchPriority PartialMatch = True
184
goodMatchPriority _ = False
185

    
186
-- | Is the lookup result an actual match?
187
goodLookupResult :: LookupResult -> Bool
188
goodLookupResult = goodMatchPriority . lrMatchPriority
189

    
190
-- | Compares a canonical name and a lookup string.
191
compareNameComponent :: String        -- ^ Canonical (target) name
192
                     -> String        -- ^ Partial (lookup) name
193
                     -> LookupResult  -- ^ Result of the lookup
194
compareNameComponent cnl lkp =
195
  select (LookupResult FailMatch lkp)
196
  [ (cnl == lkp          , LookupResult ExactMatch cnl)
197
  , (prefixMatch lkp cnl , LookupResult PartialMatch cnl)
198
  ]
199

    
200
-- | Lookup a string and choose the best result.
201
chooseLookupResult :: String       -- ^ Lookup key
202
                   -> String       -- ^ String to compare to the lookup key
203
                   -> LookupResult -- ^ Previous result
204
                   -> LookupResult -- ^ New result
205
chooseLookupResult lkp cstr old =
206
  -- default: use class order to pick the minimum result
207
  select (min new old)
208
  -- special cases:
209
  -- short circuit if the new result is an exact match
210
  [ (lrMatchPriority new == ExactMatch, new)
211
  -- if both are partial matches generate a multiple match
212
  , (partial2, LookupResult MultipleMatch lkp)
213
  ] where new = compareNameComponent cstr lkp
214
          partial2 = all ((PartialMatch==) . lrMatchPriority) [old, new]
215

    
216
-- | Find the canonical name for a lookup string in a list of names.
217
lookupName :: [String]      -- ^ List of keys
218
           -> String        -- ^ Lookup string
219
           -> LookupResult  -- ^ Result of the lookup
220
lookupName l s = foldr (chooseLookupResult s)
221
                       (LookupResult FailMatch s) l