Fix parsing of drbdsetup show output for DRBD 8.4
[ganeti-local] / lib / ht.py
index d5ae544..a452239 100644 (file)
--- a/lib/ht.py
+++ b/lib/ht.py
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2010, 2011 Google Inc.
+# Copyright (C) 2010, 2011, 2012 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -22,6 +22,7 @@
 """Module implementing the parameter types code."""
 
 import re
+import operator
 
 from ganeti import compat
 from ganeti import utils
@@ -45,6 +46,44 @@ def Parens(text):
     return "(%s)" % text
 
 
+class _WrapperBase(object):
+  __slots__ = [
+    "_fn",
+    "_text",
+    ]
+
+  def __init__(self, text, fn):
+    """Initializes this class.
+
+    @param text: Description
+    @param fn: Wrapped function
+
+    """
+    assert text.strip()
+
+    self._text = text
+    self._fn = fn
+
+  def __call__(self, *args):
+    return self._fn(*args)
+
+
+class _DescWrapper(_WrapperBase):
+  """Wrapper class for description text.
+
+  """
+  def __str__(self):
+    return self._text
+
+
+class _CommentWrapper(_WrapperBase):
+  """Wrapper class for comment.
+
+  """
+  def __str__(self):
+    return "%s [%s]" % (self._fn, self._text)
+
+
 def WithDesc(text):
   """Builds wrapper class with description text.
 
@@ -55,21 +94,20 @@ def WithDesc(text):
   """
   assert text[0] == text[0].upper()
 
-  class wrapper(object): # pylint: disable-msg=C0103
-    __slots__ = ["__call__"]
+  return compat.partial(_DescWrapper, text)
 
-    def __init__(self, fn):
-      """Initializes this class.
 
-      @param fn: Wrapped function
+def Comment(text):
+  """Builds wrapper for adding comment to description text.
 
-      """
-      self.__call__ = fn
+  @type text: string
+  @param text: Comment text
+  @return: Callable class
 
-    def __str__(self):
-      return text
+  """
+  assert not frozenset(text).intersection("[]")
 
-  return wrapper
+  return compat.partial(_CommentWrapper, text)
 
 
 def CombinationDesc(op, args, fn):
@@ -83,6 +121,12 @@ def CombinationDesc(op, args, fn):
   @param fn: Wrapped function
 
   """
+  # Some type descriptions are rather long. If "None" is listed at the
+  # end or somewhere in between it is easily missed. Therefore it should
+  # be at the beginning, e.g. "None or (long description)".
+  if __debug__ and TNone in args and args.index(TNone) > 0:
+    raise Exception("TNone must be listed first")
+
   if len(args) == 1:
     descr = str(args[0])
   else:
@@ -143,6 +187,14 @@ def TNone(val):
   return val is None
 
 
+@WithDesc("ValueNone")
+def TValueNone(val):
+  """Checks if the given value is L{constants.VALUE_NONE}.
+
+  """
+  return val == constants.VALUE_NONE
+
+
 @WithDesc("Boolean")
 def TBool(val):
   """Checks if the given value is a boolean.
@@ -161,7 +213,7 @@ def TInt(val):
   #
   # >>> (isinstance(False, int), isinstance(True, int))
   # (True, True)
-  return isinstance(val, int) and not isinstance(val, bool)
+  return isinstance(val, (int, long)) and not isinstance(val, bool)
 
 
 @WithDesc("Float")
@@ -207,6 +259,14 @@ def TList(val):
   return isinstance(val, list)
 
 
+@WithDesc("Tuple")
+def TTuple(val):
+  """Checks if the given value is a tuple.
+
+  """
+  return isinstance(val, tuple)
+
+
 @WithDesc("Dictionary")
 def TDict(val):
   """Checks if the given value is a dictionary.
@@ -237,7 +297,7 @@ def TAnd(*args):
 
 
 def TOr(*args):
-  """Combine multiple functions using an AND operation.
+  """Combine multiple functions using an OR operation.
 
   """
   def fn(val):
@@ -266,35 +326,82 @@ def TRegex(pobj):
   return desc(TAnd(TString, pobj.match))
 
 
+def TMaybe(test):
+  """Wrap a test in a TOr(TNone, test).
+
+  This makes it easier to define TMaybe* types.
+
+  """
+  return TOr(TNone, test)
+
+
+def TMaybeValueNone(test):
+  """Used for unsetting values.
+
+  """
+  return TMaybe(TOr(TValueNone, test))
+
+
 # Type aliases
 
 #: a non-empty string
 TNonEmptyString = WithDesc("NonEmptyString")(TAnd(TString, TTrue))
 
 #: a maybe non-empty string
-TMaybeString = TOr(TNonEmptyString, TNone)
+TMaybeString = TMaybe(TNonEmptyString)
 
 #: a maybe boolean (bool or none)
-TMaybeBool = TOr(TBool, TNone)
+TMaybeBool = TMaybe(TBool)
 
 #: Maybe a dictionary (dict or None)
-TMaybeDict = TOr(TDict, TNone)
+TMaybeDict = TMaybe(TDict)
 
-#: a positive integer
-TPositiveInt = \
-  TAnd(TInt, WithDesc("EqualGreaterZero")(lambda v: v >= 0))
+#: Maybe a list (list or None)
+TMaybeList = TMaybe(TList)
+
+#: a non-negative integer (value >= 0)
+TNonNegativeInt = \
+  TAnd(TInt, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0))
 
-#: a strictly positive integer
-TStrictPositiveInt = \
+#: a positive integer (value > 0)
+TPositiveInt = \
   TAnd(TInt, WithDesc("GreaterThanZero")(lambda v: v > 0))
 
+#: a maybe positive integer (positive integer or None)
+TMaybePositiveInt = TMaybe(TPositiveInt)
+
+#: a negative integer (value < 0)
+TNegativeInt = \
+  TAnd(TInt, WithDesc("LessThanZero")(compat.partial(operator.gt, 0)))
+
 #: a positive float
-TPositiveFloat = \
-  TAnd(TFloat, WithDesc("EqualGreaterZero")(lambda v: v >= 0.0))
+TNonNegativeFloat = \
+  TAnd(TFloat, WithDesc("EqualOrGreaterThanZero")(lambda v: v >= 0.0))
 
 #: Job ID
-TJobId = TOr(TPositiveInt,
-             TRegex(re.compile("^%s$" % constants.JOB_ID_TEMPLATE)))
+TJobId = WithDesc("JobId")(TOr(TNonNegativeInt,
+                               TRegex(re.compile("^%s$" %
+                                                 constants.JOB_ID_TEMPLATE))))
+
+#: Number
+TNumber = TOr(TInt, TFloat)
+
+#: Relative job ID
+TRelativeJobId = WithDesc("RelativeJobId")(TNegativeInt)
+
+
+def TInstanceOf(cls):
+  """Checks if a given value is an instance of C{cls}.
+
+  @type cls: class
+  @param cls: Class object
+
+  """
+  name = "%s.%s" % (cls.__module__, cls.__name__)
+
+  desc = WithDesc("Instance of %s" % (Parens(name), ))
+
+  return desc(lambda val: isinstance(val, cls))
 
 
 def TListOf(my_type):
@@ -305,6 +412,9 @@ def TListOf(my_type):
   return desc(TAnd(TList, lambda lst: compat.all(my_type(v) for v in lst)))
 
 
+TMaybeListOf = lambda item_type: TMaybe(TListOf(item_type))
+
+
 def TDictOf(key_type, val_type):
   """Checks a dict type for the type of its key/values.