Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / BasicTypes.hs @ ea128e20

History | View | Annotate | Download (8.1 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
  , justOk
34
  , justBad
35
  , eitherToResult
36
  , annotateResult
37
  , iterateOk
38
  , select
39
  , LookupResult(..)
40
  , MatchPriority(..)
41
  , lookupName
42
  , goodLookupResult
43
  , goodMatchPriority
44
  , prefixMatch
45
  , compareNameComponent
46
  , ListSet(..)
47
  , emptyListSet
48
  ) where
49

    
50
import Control.Applicative
51
import Control.Monad
52
import Control.Monad.Trans
53
import Data.Function
54
import Data.List
55
import Data.Maybe
56
import Data.Set (Set)
57
import qualified Data.Set as Set (empty)
58
import Text.JSON (JSON)
59
import qualified Text.JSON as JSON (readJSON, showJSON)
60

    
61
-- | Generic monad for our error handling mechanisms.
62
data GenericResult a b
63
  = Bad a
64
  | Ok b
65
    deriving (Show, Eq)
66

    
67
-- | Sum type structure of GenericResult.
68
genericResult :: (a -> c) -> (b -> c) -> GenericResult a b -> c
69
genericResult f _ (Bad a) = f a
70
genericResult _ g (Ok b) = g b
71

    
72
-- | Type alias for a string Result.
73
type Result = GenericResult String
74

    
75
-- | Type class for things that can be built from strings.
76
class FromString a where
77
  mkFromString :: String -> a
78

    
79
-- | Trivial 'String' instance; requires FlexibleInstances extension
80
-- though.
81
instance FromString [Char] where
82
  mkFromString = id
83

    
84
-- | 'Monad' instance for 'GenericResult'.
85
instance (FromString a) => Monad (GenericResult a) where
86
  (>>=) (Bad x) _ = Bad x
87
  (>>=) (Ok x) fn = fn x
88
  return = Ok
89
  fail   = Bad . mkFromString
90

    
91
instance Functor (GenericResult a) where
92
  fmap _ (Bad msg) = Bad msg
93
  fmap fn (Ok val) = Ok (fn val)
94

    
95
instance MonadPlus (GenericResult String) where
96
  mzero = Bad "zero Result when used as MonadPlus"
97
  -- for mplus, when we 'add' two Bad values, we concatenate their
98
  -- error descriptions
99
  (Bad x) `mplus` (Bad y) = Bad (x ++ "; " ++ y)
100
  (Bad _) `mplus` x = x
101
  x@(Ok _) `mplus` _ = x
102

    
103
instance Applicative (GenericResult a) where
104
  pure = Ok
105
  (Bad f) <*> _       = Bad f
106
  _       <*> (Bad x) = Bad x
107
  (Ok f)  <*> (Ok x)  = Ok $ f x
108

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

    
113
instance (Monad m, FromString a) => Monad (ResultT a m) where
114
  fail err = ResultT (return . Bad $ mkFromString err)
115
  return   = lift . return
116
  x >>= f  = ResultT $ do
117
               a <- runResultT x
118
               case a of
119
                 Ok val -> runResultT $ f val
120
                 Bad err -> return $ Bad err
121

    
122
instance MonadTrans (ResultT a) where
123
  lift x = ResultT (liftM Ok x)
124

    
125
instance (MonadIO m, FromString a) => MonadIO (ResultT a m) where
126
  liftIO = lift . liftIO
127

    
128
-- | Lift a `Result` value to a `ResultT`.
129
resultT :: Monad m => GenericResult a b -> ResultT a m b
130
resultT = ResultT . return
131

    
132
-- | Simple checker for whether a 'GenericResult' is OK.
133
isOk :: GenericResult a b -> Bool
134
isOk (Ok _) = True
135
isOk _      = False
136

    
137
-- | Simple checker for whether a 'GenericResult' is a failure.
138
isBad :: GenericResult a b -> Bool
139
isBad = not . isOk
140

    
141
-- | Simple filter returning only OK values of GenericResult
142
justOk :: [GenericResult a b] -> [b]
143
justOk = mapMaybe (genericResult (const Nothing) Just)
144

    
145
-- | Simple filter returning only Bad values of GenericResult
146
justBad :: [GenericResult a b] -> [a]
147
justBad = mapMaybe (genericResult Just (const Nothing))
148

    
149
-- | Converter from Either to 'GenericResult'.
150
eitherToResult :: Either a b -> GenericResult a b
151
eitherToResult (Left  s) = Bad s
152
eitherToResult (Right v) = Ok  v
153

    
154
-- | Annotate a Result with an ownership information.
155
annotateResult :: String -> Result a -> Result a
156
annotateResult owner (Bad s) = Bad $ owner ++ ": " ++ s
157
annotateResult _ v = v
158

    
159
-- | Iterate while Ok.
160
iterateOk :: (a -> GenericResult b a) -> a -> [a]
161
iterateOk f a = genericResult (const []) ((:) a . iterateOk f) (f a)
162

    
163
-- * Misc functionality
164

    
165
-- | Return the first result with a True condition, or the default otherwise.
166
select :: a            -- ^ default result
167
       -> [(Bool, a)]  -- ^ list of \"condition, result\"
168
       -> a            -- ^ first result which has a True condition, or default
169
select def = maybe def snd . find fst
170

    
171
-- * Lookup of partial names functionality
172

    
173
-- | The priority of a match in a lookup result.
174
data MatchPriority = ExactMatch
175
                   | MultipleMatch
176
                   | PartialMatch
177
                   | FailMatch
178
                   deriving (Show, Enum, Eq, Ord)
179

    
180
-- | The result of a name lookup in a list.
181
data LookupResult = LookupResult
182
  { lrMatchPriority :: MatchPriority -- ^ The result type
183
  -- | Matching value (for ExactMatch, PartialMatch), Lookup string otherwise
184
  , lrContent :: String
185
  } deriving (Show)
186

    
187
-- | Lookup results have an absolute preference ordering.
188
instance Eq LookupResult where
189
  (==) = (==) `on` lrMatchPriority
190

    
191
instance Ord LookupResult where
192
  compare = compare `on` lrMatchPriority
193

    
194
-- | Check for prefix matches in names.
195
-- Implemented in Ganeti core utils.text.MatchNameComponent
196
-- as the regexp r"^%s(\..*)?$" % re.escape(key)
197
prefixMatch :: String  -- ^ Lookup
198
            -> String  -- ^ Full name
199
            -> Bool    -- ^ Whether there is a prefix match
200
prefixMatch = isPrefixOf . (++ ".")
201

    
202
-- | Is the lookup priority a "good" one?
203
goodMatchPriority :: MatchPriority -> Bool
204
goodMatchPriority ExactMatch = True
205
goodMatchPriority PartialMatch = True
206
goodMatchPriority _ = False
207

    
208
-- | Is the lookup result an actual match?
209
goodLookupResult :: LookupResult -> Bool
210
goodLookupResult = goodMatchPriority . lrMatchPriority
211

    
212
-- | Compares a canonical name and a lookup string.
213
compareNameComponent :: String        -- ^ Canonical (target) name
214
                     -> String        -- ^ Partial (lookup) name
215
                     -> LookupResult  -- ^ Result of the lookup
216
compareNameComponent cnl lkp =
217
  select (LookupResult FailMatch lkp)
218
  [ (cnl == lkp          , LookupResult ExactMatch cnl)
219
  , (prefixMatch lkp cnl , LookupResult PartialMatch cnl)
220
  ]
221

    
222
-- | Lookup a string and choose the best result.
223
chooseLookupResult :: String       -- ^ Lookup key
224
                   -> String       -- ^ String to compare to the lookup key
225
                   -> LookupResult -- ^ Previous result
226
                   -> LookupResult -- ^ New result
227
chooseLookupResult lkp cstr old =
228
  -- default: use class order to pick the minimum result
229
  select (min new old)
230
  -- special cases:
231
  -- short circuit if the new result is an exact match
232
  [ (lrMatchPriority new == ExactMatch, new)
233
  -- if both are partial matches generate a multiple match
234
  , (partial2, LookupResult MultipleMatch lkp)
235
  ] where new = compareNameComponent cstr lkp
236
          partial2 = all ((PartialMatch==) . lrMatchPriority) [old, new]
237

    
238
-- | Find the canonical name for a lookup string in a list of names.
239
lookupName :: [String]      -- ^ List of keys
240
           -> String        -- ^ Lookup string
241
           -> LookupResult  -- ^ Result of the lookup
242
lookupName l s = foldr (chooseLookupResult s)
243
                       (LookupResult FailMatch s) l
244

    
245
-- | Wrapper for a Haskell 'Set'
246
--
247
-- This type wraps a 'Set' and it is used in the Haskell to Python
248
-- opcode generation to transform a Haskell 'Set' into a Python 'list'
249
-- without duplicate elements.
250
newtype ListSet a = ListSet { unListSet :: Set a }
251
  deriving (Eq, Show)
252

    
253
instance (Ord a, JSON a) => JSON (ListSet a) where
254
  showJSON = JSON.showJSON . unListSet
255
  readJSON = liftM ListSet . JSON.readJSON
256

    
257
emptyListSet :: ListSet a
258
emptyListSet = ListSet Set.empty