text.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. # -*- coding: utf-8 -*-
  2. """Text formatting utilities."""
  3. from __future__ import absolute_import, unicode_literals
  4. import re
  5. from collections import Callable
  6. from functools import partial
  7. from textwrap import fill
  8. from pprint import pformat
  9. from celery.five import string_t
  10. __all__ = [
  11. 'abbr', 'abbrtask', 'dedent', 'dedent_initial',
  12. 'ensure_newlines', 'ensure_sep',
  13. 'fill_paragraphs', 'indent', 'join',
  14. 'pluralize', 'pretty', 'str_to_list', 'simple_format', 'truncate',
  15. ]
  16. UNKNOWN_SIMPLE_FORMAT_KEY = """
  17. Unknown format %{0} in string {1!r}.
  18. Possible causes: Did you forget to escape the expand sign (use '%%{0!r}'),
  19. or did you escape and the value was expanded twice? (%%N -> %N -> %hostname)?
  20. """.strip()
  21. RE_FORMAT = re.compile(r'%(\w)')
  22. def str_to_list(s):
  23. # type: (str) -> List[str]
  24. """Convert string to list."""
  25. if isinstance(s, string_t):
  26. return s.split(',')
  27. return s
  28. def dedent_initial(s, n=4):
  29. # type: (str, int) -> str
  30. """Remove identation from first line of text."""
  31. return s[n:] if s[:n] == ' ' * n else s
  32. def dedent(s, n=4, sep='\n'):
  33. # type: (str, int, str) -> str
  34. """Remove identation."""
  35. return sep.join(dedent_initial(l) for l in s.splitlines())
  36. def fill_paragraphs(s, width, sep='\n'):
  37. # type: (str, int, str) -> str
  38. """Fill paragraphs with newlines (or custom separator)."""
  39. return sep.join(fill(p, width) for p in s.split(sep))
  40. def join(l, sep='\n'):
  41. # type: (str, str) -> str
  42. """Concatenate list of strings."""
  43. return sep.join(v for v in l if v)
  44. def ensure_sep(sep, s, n=2):
  45. # type: (str, str, int) -> str
  46. """Ensure text s ends in separator sep'."""
  47. return s + sep * (n - s.count(sep))
  48. ensure_newlines = partial(ensure_sep, '\n')
  49. def abbr(S, max, ellipsis='...'):
  50. # type: (str, int, str) -> str
  51. """Abbreviate word."""
  52. if S is None:
  53. return '???'
  54. if len(S) > max:
  55. return ellipsis and (S[:max - len(ellipsis)] + ellipsis) or S[:max]
  56. return S
  57. def abbrtask(S, max):
  58. # type: (str, int) -> str
  59. """Abbreviate task name."""
  60. if S is None:
  61. return '???'
  62. if len(S) > max:
  63. module, _, cls = S.rpartition('.')
  64. module = abbr(module, max - len(cls) - 3, False)
  65. return module + '[.]' + cls
  66. return S
  67. def indent(t, indent=0, sep='\n'):
  68. # type: (str, int, str) -> str
  69. """Indent text."""
  70. return sep.join(' ' * indent + p for p in t.split(sep))
  71. def truncate(s, maxlen=128, suffix='...'):
  72. # type: (str, int, str) -> str
  73. """Truncate text to a maximum number of characters."""
  74. if maxlen and len(s) >= maxlen:
  75. return s[:maxlen].rsplit(' ', 1)[0] + suffix
  76. return s
  77. def truncate_bytes(s, maxlen=128, suffix=b'...'):
  78. # type: (bytes, int, bytes) -> bytes
  79. if maxlen and len(s) >= maxlen:
  80. return s[:maxlen].rsplit(b' ', 1)[0] + suffix
  81. return s
  82. def pluralize(n, text, suffix='s'):
  83. # type: (int, str, str) -> str
  84. """Pluralize term when n is greater than one."""
  85. if n != 1:
  86. return text + suffix
  87. return text
  88. def pretty(value, width=80, nl_width=80, sep='\n', **kw):
  89. # type: (str, int, int, str, **Any) -> str
  90. """Format value for printing to console."""
  91. if isinstance(value, dict):
  92. return '{{{0} {1}'.format(sep, pformat(value, 4, nl_width)[1:])
  93. elif isinstance(value, tuple):
  94. return '{0}{1}{2}'.format(
  95. sep, ' ' * 4, pformat(value, width=nl_width, **kw),
  96. )
  97. else:
  98. return pformat(value, width=width, **kw)
  99. def match_case(s, other):
  100. # type: (str, str) -> str
  101. return s.upper() if other.isupper() else s.lower()
  102. def simple_format(s, keys, pattern=RE_FORMAT, expand=r'\1'):
  103. # type: (str, Mapping[str, str], Pattern, str) -> str
  104. """Format string, expanding abbreviations in keys'."""
  105. if s:
  106. keys.setdefault('%', '%')
  107. def resolve(match):
  108. key = match.expand(expand)
  109. try:
  110. resolver = keys[key]
  111. except KeyError:
  112. raise ValueError(UNKNOWN_SIMPLE_FORMAT_KEY.format(key, s))
  113. if isinstance(resolver, Callable):
  114. return resolver()
  115. return resolver
  116. return pattern.sub(resolve, s)
  117. return s