opcodes: Annotate the OP_RESULT of query operations
[ganeti-local] / lib / utils / text.py
index bfce363..0a0e68c 100644 (file)
@@ -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._+/:%@]+$")
 
 #: 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.
 
 def MatchNameComponent(key, name_list, case_sensitive=True):
   """Try to match a name against a list.
@@ -268,12 +271,16 @@ class ShellWriter:
     """
     assert self._indent >= 0
 
     """
     assert self._indent >= 0
 
-    self._fh.write(self._indent * self.INDENT_STR)
-
     if args:
     if args:
-      self._fh.write(txt % args)
+      line = txt % args
     else:
     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")
 
 
     self._fh.write("\n")
 
@@ -338,7 +345,7 @@ def SafeEncode(text):
   resu = ""
   for char in text:
     c = ord(char)
   resu = ""
   for char in text:
     c = ord(char)
-    if char  == "\t":
+    if char == "\t":
       resu += r"\t"
     elif char == "\n":
       resu += r"\n"
       resu += r"\t"
     elif char == "\n":
       resu += r"\n"
@@ -381,12 +388,14 @@ def UnescapeAndSplit(text, sep=","):
     e1 = slist.pop(0)
     if e1.endswith("\\"):
       num_b = len(e1) - len(e1.rstrip("\\"))
     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)
         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
         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]
     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 = \
     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
 
     else:
       self._line_fn = line_fn
 
@@ -550,3 +559,26 @@ def FormatOrdinal(value):
     suffix = "th"
 
   return "%s%s" % (value, suffix)
     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