From 5401c39d7d8a9f1dd83970fc7a70354f387aef4c Mon Sep 17 00:00:00 2001
From: Michael Hanselmann <hansmi@google.com>
Date: Fri, 17 Feb 2012 13:42:04 +0100
Subject: [PATCH] utils.text: Add function to truncate string
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The function adds an ellipse if the string was actually truncated. Also
start using it in mcpu for result checks (where the message is also
slightly changed to use a colon).

Signed-off-by: Michael Hanselmann <hansmi@google.com>
Reviewed-by: RenΓ© Nussbaumer <rn@google.com>
---
 lib/mcpu.py                        |  4 ++--
 lib/utils/text.py                  | 26 ++++++++++++++++++++++++++
 test/ganeti.utils.text_unittest.py | 27 +++++++++++++++++++++++++++
 3 files changed, 55 insertions(+), 2 deletions(-)

diff --git a/lib/mcpu.py b/lib/mcpu.py
index 96c589b7f..8b3734e0e 100644
--- a/lib/mcpu.py
+++ b/lib/mcpu.py
@@ -404,8 +404,8 @@ class Processor(object):
     if not (resultcheck_fn is None or resultcheck_fn(result)):
       logging.error("Expected opcode result matching %s, got %s",
                     resultcheck_fn, result)
-      raise errors.OpResultError("Opcode result does not match %s, got %s" %
-                                 (resultcheck_fn, result[:80]))
+      raise errors.OpResultError("Opcode result does not match %s: %s" %
+                                 (resultcheck_fn, utils.Truncate(result, 80)))
 
     return result
 
diff --git a/lib/utils/text.py b/lib/utils/text.py
index 02382642b..0a0e68ceb 100644
--- a/lib/utils/text.py
+++ b/lib/utils/text.py
@@ -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.
@@ -556,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
diff --git a/test/ganeti.utils.text_unittest.py b/test/ganeti.utils.text_unittest.py
index 1e91468e3..5a9af02e7 100755
--- a/test/ganeti.utils.text_unittest.py
+++ b/test/ganeti.utils.text_unittest.py
@@ -545,5 +545,32 @@ class TestOrdinal(unittest.TestCase):
       self.assertEqual(utils.FormatOrdinal(value), ordinal)
 
 
+class TestTruncate(unittest.TestCase):
+  def _Test(self, text, length):
+    result = utils.Truncate(text, length)
+    self.assertTrue(len(result) <= length)
+    return result
+
+  def test(self):
+    self.assertEqual(self._Test("", 80), "")
+    self.assertEqual(self._Test("abc", 4), "abc")
+    self.assertEqual(self._Test("Hello World", 80), "Hello World")
+    self.assertEqual(self._Test("Hello World", 4), "H...")
+    self.assertEqual(self._Test("Hello World", 5), "He...")
+
+    for i in [4, 10, 100]:
+      data = i * "FooBarBaz"
+      self.assertEqual(self._Test(data, len(data)), data)
+
+    for (length, exp) in [(8, u"T\u00e4st\u2026xyz"), (7, u"T\u00e4st...")]:
+      self.assertEqual(self._Test(u"T\u00e4st\u2026xyz", length), exp)
+
+    self.assertEqual(self._Test(range(100), 20), "[0, 1, 2, 3, 4, 5...")
+
+  def testError(self):
+    for i in range(4):
+      self.assertRaises(AssertionError, utils.Truncate, "", i)
+
+
 if __name__ == "__main__":
   testutils.GanetiTestProgram()
-- 
GitLab