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