Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / BasicTypes.hs @ 4651c69f

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

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

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

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

    
69
-- | Type alias for a string Result.
70
type Result = GenericResult String
71

    
72
-- | Type class for things that can be built from strings.
73
class FromString a where
74
  mkFromString :: String -> a
75

    
76
-- | Trivial 'String' instance; requires FlexibleInstances extension
77
-- though.
78
instance FromString [Char] where
79
  mkFromString = id
80

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

    
88
instance Functor (GenericResult a) where
89
  fmap _ (Bad msg) = Bad msg
90
  fmap fn (Ok val) = Ok (fn val)
91

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

    
100
instance Applicative (GenericResult a) where
101
  pure = Ok
102
  (Bad f) <*> _       = Bad f
103
  _       <*> (Bad x) = Bad x
104
  (Ok f)  <*> (Ok x)  = Ok $ f x
105

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

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

    
119
instance MonadTrans (ResultT a) where
120
  lift x = ResultT (liftM Ok x)
121

    
122
instance (MonadIO m, FromString a) => MonadIO (ResultT a m) where
123
  liftIO = lift . liftIO
124

    
125
-- | Lift a `Result` value to a `ResultT`.
126
resultT :: Monad m => GenericResult a b -> ResultT a m b
127
resultT = ResultT . return
128

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

    
134
-- | Simple checker for whether a 'GenericResult' is a failure.
135
isBad :: GenericResult a b -> Bool
136
isBad = not . isOk
137

    
138
-- | Converter from Either to 'GenericResult'.
139
eitherToResult :: Either a b -> GenericResult a b
140
eitherToResult (Left  s) = Bad s
141
eitherToResult (Right v) = Ok  v
142

    
143
-- | Annotate a Result with an ownership information.
144
annotateResult :: String -> Result a -> Result a
145
annotateResult owner (Bad s) = Bad $ owner ++ ": " ++ s
146
annotateResult _ v = v
147

    
148
-- | Iterate while Ok.
149
iterateOk :: (a -> GenericResult b a) -> a -> [a]
150
iterateOk f a = genericResult (const []) ((:) a . iterateOk f) (f a)
151

    
152
-- * Misc functionality
153

    
154
-- | Return the first result with a True condition, or the default otherwise.
155
select :: a            -- ^ default result
156
       -> [(Bool, a)]  -- ^ list of \"condition, result\"
157
       -> a            -- ^ first result which has a True condition, or default
158
select def = maybe def snd . find fst
159

    
160
-- * Lookup of partial names functionality
161

    
162
-- | The priority of a match in a lookup result.
163
data MatchPriority = ExactMatch
164
                   | MultipleMatch
165
                   | PartialMatch
166
                   | FailMatch
167
                   deriving (Show, Enum, Eq, Ord)
168

    
169
-- | The result of a name lookup in a list.
170
data LookupResult = LookupResult
171
  { lrMatchPriority :: MatchPriority -- ^ The result type
172
  -- | Matching value (for ExactMatch, PartialMatch), Lookup string otherwise
173
  , lrContent :: String
174
  } deriving (Show)
175

    
176
-- | Lookup results have an absolute preference ordering.
177
instance Eq LookupResult where
178
  (==) = (==) `on` lrMatchPriority
179

    
180
instance Ord LookupResult where
181
  compare = compare `on` lrMatchPriority
182

    
183
-- | Check for prefix matches in names.
184
-- Implemented in Ganeti core utils.text.MatchNameComponent
185
-- as the regexp r"^%s(\..*)?$" % re.escape(key)
186
prefixMatch :: String  -- ^ Lookup
187
            -> String  -- ^ Full name
188
            -> Bool    -- ^ Whether there is a prefix match
189
prefixMatch = isPrefixOf . (++ ".")
190

    
191
-- | Is the lookup priority a "good" one?
192
goodMatchPriority :: MatchPriority -> Bool
193
goodMatchPriority ExactMatch = True
194
goodMatchPriority PartialMatch = True
195
goodMatchPriority _ = False
196

    
197
-- | Is the lookup result an actual match?
198
goodLookupResult :: LookupResult -> Bool
199
goodLookupResult = goodMatchPriority . lrMatchPriority
200

    
201
-- | Compares a canonical name and a lookup string.
202
compareNameComponent :: String        -- ^ Canonical (target) name
203
                     -> String        -- ^ Partial (lookup) name
204
                     -> LookupResult  -- ^ Result of the lookup
205
compareNameComponent cnl lkp =
206
  select (LookupResult FailMatch lkp)
207
  [ (cnl == lkp          , LookupResult ExactMatch cnl)
208
  , (prefixMatch lkp cnl , LookupResult PartialMatch cnl)
209
  ]
210

    
211
-- | Lookup a string and choose the best result.
212
chooseLookupResult :: String       -- ^ Lookup key
213
                   -> String       -- ^ String to compare to the lookup key
214
                   -> LookupResult -- ^ Previous result
215
                   -> LookupResult -- ^ New result
216
chooseLookupResult lkp cstr old =
217
  -- default: use class order to pick the minimum result
218
  select (min new old)
219
  -- special cases:
220
  -- short circuit if the new result is an exact match
221
  [ (lrMatchPriority new == ExactMatch, new)
222
  -- if both are partial matches generate a multiple match
223
  , (partial2, LookupResult MultipleMatch lkp)
224
  ] where new = compareNameComponent cstr lkp
225
          partial2 = all ((PartialMatch==) . lrMatchPriority) [old, new]
226

    
227
-- | Find the canonical name for a lookup string in a list of names.
228
lookupName :: [String]      -- ^ List of keys
229
           -> String        -- ^ Lookup string
230
           -> LookupResult  -- ^ Result of the lookup
231
lookupName l s = foldr (chooseLookupResult s)
232
                       (LookupResult FailMatch s) l
233

    
234
-- | Wrapper for a Haskell 'Set'
235
--
236
-- This type wraps a 'Set' and it is used in the Haskell to Python
237
-- opcode generation to transform a Haskell 'Set' into a Python 'list'
238
-- without duplicate elements.
239
newtype ListSet a = ListSet { unListSet :: Set a }
240
  deriving (Eq, Show)
241

    
242
instance (Ord a, JSON a) => JSON (ListSet a) where
243
  showJSON = JSON.showJSON . unListSet
244
  readJSON = liftM ListSet . JSON.readJSON
245

    
246
emptyListSet :: ListSet a
247
emptyListSet = ListSet Set.empty