opcodes: Annotate the OP_RESULT of query operations
[ganeti-local] / lib / utils / text.py
index d8e0984..0a0e68c 100644 (file)
@@ -35,7 +35,7 @@ from ganeti import errors
 _PARSEUNIT_REGEX = re.compile(r"^([.\d]+)\s*([a-zA-Z]+)?$")
 
 #: Characters which don't need to be quoted for shell commands
-_SHELL_UNQUOTED_RE = re.compile('^[-.,=:/_+@A-Za-z0-9]+$')
+_SHELL_UNQUOTED_RE = re.compile("^[-.,=:/_+@A-Za-z0-9]+$")
 
 #: MAC checker regexp
 _MAC_CHECK_RE = re.compile("^([0-9a-f]{2}:){5}[0-9a-f]{2}$", re.I)
@@ -43,6 +43,9 @@ _MAC_CHECK_RE = re.compile("^([0-9a-f]{2}:){5}[0-9a-f]{2}$", re.I)
 #: Shell param checker regexp
 _SHELLPARAM_REGEX = re.compile(r"^[-a-zA-Z0-9._+/:%@]+$")
 
+#: ASCII equivalent of unicode character 'HORIZONTAL ELLIPSIS' (U+2026)
+_ASCII_ELLIPSIS = "..."
+
 
 def MatchNameComponent(key, name_list, case_sensitive=True):
   """Try to match a name against a list.
@@ -143,24 +146,24 @@ def FormatUnit(value, units):
   @return: the formatted value (with suffix)
 
   """
-  if units not in ('m', 'g', 't', 'h'):
+  if units not in ("m", "g", "t", "h"):
     raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
 
-  suffix = ''
+  suffix = ""
 
-  if units == 'm' or (units == 'h' and value < 1024):
-    if units == 'h':
-      suffix = 'M'
+  if units == "m" or (units == "h" and value < 1024):
+    if units == "h":
+      suffix = "M"
     return "%d%s" % (round(value, 0), suffix)
 
-  elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
-    if units == 'h':
-      suffix = 'G'
+  elif units == "g" or (units == "h" and value < (1024 * 1024)):
+    if units == "h":
+      suffix = "G"
     return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
 
   else:
-    if units == 'h':
-      suffix = 'T'
+    if units == "h":
+      suffix = "T"
     return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
 
 
@@ -182,16 +185,16 @@ def ParseUnit(input_string):
   if unit:
     lcunit = unit.lower()
   else:
-    lcunit = 'm'
+    lcunit = "m"
 
-  if lcunit in ('m', 'mb', 'mib'):
+  if lcunit in ("m", "mb", "mib"):
     # Value already in MiB
     pass
 
-  elif lcunit in ('g', 'gb', 'gib'):
+  elif lcunit in ("g", "gb", "gib"):
     value *= 1024
 
-  elif lcunit in ('t', 'tb', 'tib'):
+  elif lcunit in ("t", "tb", "tib"):
     value *= 1024 * 1024
 
   else:
@@ -268,12 +271,16 @@ class ShellWriter:
     """
     assert self._indent >= 0
 
-    self._fh.write(self._indent * self.INDENT_STR)
-
     if args:
-      self._fh.write(txt % args)
+      line = txt % args
     else:
-      self._fh.write(txt)
+      line = txt
+
+    if line:
+      # Indent only if there's something on the line
+      self._fh.write(self._indent * self.INDENT_STR)
+
+    self._fh.write(line)
 
     self._fh.write("\n")
 
@@ -334,15 +341,15 @@ def SafeEncode(text):
   """
   if isinstance(text, unicode):
     # only if unicode; if str already, we handle it below
-    text = text.encode('ascii', 'backslashreplace')
+    text = text.encode("ascii", "backslashreplace")
   resu = ""
   for char in text:
     c = ord(char)
-    if char  == '\t':
-      resu += r'\t'
-    elif char == '\n':
-      resu += r'\n'
-    elif char == '\r':
+    if char == "\t":
+      resu += r"\t"
+    elif char == "\n":
+      resu += r"\n"
+    elif char == "\r":
       resu += r'\'r'
     elif c < 32 or c >= 127: # non-printable
       resu += "\\x%02x" % (c & 0xff)
@@ -381,12 +388,14 @@ def UnescapeAndSplit(text, sep=","):
     e1 = slist.pop(0)
     if e1.endswith("\\"):
       num_b = len(e1) - len(e1.rstrip("\\"))
-      if num_b % 2 == 1:
+      if num_b % 2 == 1 and slist:
         e2 = slist.pop(0)
-        # here the backslashes remain (all), and will be reduced in
-        # the next step
-        rlist.append(e1 + sep + e2)
+        # Merge the two elements and push the result back to the source list for
+        # revisiting. If e2 ended with backslashes, further merging may need to
+        # be done.
+        slist.insert(0, e1 + sep + e2)
         continue
+    # here the backslashes remain (all), and will be reduced in the next step
     rlist.append(e1)
   # finally, replace backslash-something with something
   rlist = [re.sub(r"\\(.)", r"\1", v) for v in rlist]
@@ -463,7 +472,7 @@ class LineSplitter:
     if args:
       # Python 2.4 doesn't have functools.partial yet
       self._line_fn = \
-        lambda line: line_fn(line, *args) # pylint: disable-msg=W0142
+        lambda line: line_fn(line, *args) # pylint: disable=W0142
     else:
       self._line_fn = line_fn
 
@@ -550,3 +559,26 @@ def FormatOrdinal(value):
     suffix = "th"
 
   return "%s%s" % (value, suffix)
+
+
+def Truncate(text, length):
+  """Truncate string and add ellipsis if needed.
+
+  @type text: string
+  @param text: Text
+  @type length: integer
+  @param length: Desired length
+  @rtype: string
+  @return: Truncated text
+
+  """
+  assert length > len(_ASCII_ELLIPSIS)
+
+  # Serialize if necessary
+  if not isinstance(text, basestring):
+    text = str(text)
+
+  if len(text) <= length:
+    return text
+  else:
+    return text[:length - len(_ASCII_ELLIPSIS)] + _ASCII_ELLIPSIS