utils.FilterEmptyLinesAndComments: Return list
[ganeti-local] / lib / utils / text.py
index 058ee85..84de0bb 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._+/:%@]+$")
 
+#: 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.
@@ -242,11 +245,12 @@ class ShellWriter:
   """
   INDENT_STR = "  "
 
-  def __init__(self, fh):
+  def __init__(self, fh, indent=True):
     """Initializes this class.
 
     """
     self._fh = fh
+    self._indent_enabled = indent
     self._indent = 0
 
   def IncIndent(self):
@@ -273,7 +277,7 @@ class ShellWriter:
     else:
       line = txt
 
-    if line:
+    if line and self._indent_enabled:
       # Indent only if there's something on the line
       self._fh.write(self._indent * self.INDENT_STR)
 
@@ -387,10 +391,12 @@ def UnescapeAndSplit(text, sep=","):
       num_b = len(e1) - len(e1.rstrip("\\"))
       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]
@@ -407,7 +413,7 @@ def CommaJoin(names):
   return ", ".join([str(val) for val in names])
 
 
-def FormatTime(val):
+def FormatTime(val, usecs=None):
   """Formats a time value.
 
   @type val: float or None
@@ -418,9 +424,15 @@ def FormatTime(val):
   """
   if val is None or not isinstance(val, (int, float)):
     return "N/A"
+
   # these two codes works on Linux, but they are not guaranteed on all
   # platforms
-  return time.strftime("%F %T", time.localtime(val))
+  result = time.strftime("%F %T", time.localtime(val))
+
+  if usecs is not None:
+    result += ".%06d" % usecs
+
+  return result
 
 
 def FormatSeconds(secs):
@@ -554,3 +566,41 @@ 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
+
+
+def FilterEmptyLinesAndComments(text):
+  """Filters empty lines and comments from a line-based string.
+
+  Whitespace is also removed from the beginning and end of all lines.
+
+  @type text: string
+  @param text: Input string
+  @rtype: list
+
+  """
+  return [line for line in map(lambda s: s.strip(), text.splitlines())
+          # Ignore empty lines and comments
+          if line and not line.startswith("#")]