Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / BasicTypes.hs @ a9ccc950

History | View | Annotate | Download (5.7 kB)

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