groff.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. """
  2. pygments.formatters.groff
  3. ~~~~~~~~~~~~~~~~~~~~~~~~~
  4. Formatter for groff output.
  5. :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
  6. :license: BSD, see LICENSE for details.
  7. """
  8. import math
  9. from pygments.formatter import Formatter
  10. from pygments.util import get_bool_opt, get_int_opt
  11. __all__ = ['GroffFormatter']
  12. class GroffFormatter(Formatter):
  13. """
  14. Format tokens with groff escapes to change their color and font style.
  15. .. versionadded:: 2.11
  16. Additional options accepted:
  17. `style`
  18. The style to use, can be a string or a Style subclass (default:
  19. ``'default'``).
  20. `monospaced`
  21. If set to true, monospace font will be used (default: ``true``).
  22. `linenos`
  23. If set to true, print the line numbers (default: ``false``).
  24. `wrap`
  25. Wrap lines to the specified number of characters. Disabled if set to 0
  26. (default: ``0``).
  27. """
  28. name = 'groff'
  29. aliases = ['groff','troff','roff']
  30. filenames = []
  31. def __init__(self, **options):
  32. Formatter.__init__(self, **options)
  33. self.monospaced = get_bool_opt(options, 'monospaced', True)
  34. self.linenos = get_bool_opt(options, 'linenos', False)
  35. self._lineno = 0
  36. self.wrap = get_int_opt(options, 'wrap', 0)
  37. self._linelen = 0
  38. self.styles = {}
  39. self._make_styles()
  40. def _make_styles(self):
  41. regular = '\\f[CR]' if self.monospaced else '\\f[R]'
  42. bold = '\\f[CB]' if self.monospaced else '\\f[B]'
  43. italic = '\\f[CI]' if self.monospaced else '\\f[I]'
  44. for ttype, ndef in self.style:
  45. start = end = ''
  46. if ndef['color']:
  47. start += '\\m[{}]'.format(ndef['color'])
  48. end = '\\m[]' + end
  49. if ndef['bold']:
  50. start += bold
  51. end = regular + end
  52. if ndef['italic']:
  53. start += italic
  54. end = regular + end
  55. if ndef['bgcolor']:
  56. start += '\\M[{}]'.format(ndef['bgcolor'])
  57. end = '\\M[]' + end
  58. self.styles[ttype] = start, end
  59. def _define_colors(self, outfile):
  60. colors = set()
  61. for _, ndef in self.style:
  62. if ndef['color'] is not None:
  63. colors.add(ndef['color'])
  64. for color in sorted(colors):
  65. outfile.write('.defcolor ' + color + ' rgb #' + color + '\n')
  66. def _write_lineno(self, outfile):
  67. self._lineno += 1
  68. outfile.write("%s% 4d " % (self._lineno != 1 and '\n' or '', self._lineno))
  69. def _wrap_line(self, line):
  70. length = len(line.rstrip('\n'))
  71. space = ' ' if self.linenos else ''
  72. newline = ''
  73. if length > self.wrap:
  74. for i in range(0, math.floor(length / self.wrap)):
  75. chunk = line[i*self.wrap:i*self.wrap+self.wrap]
  76. newline += (chunk + '\n' + space)
  77. remainder = length % self.wrap
  78. if remainder > 0:
  79. newline += line[-remainder-1:]
  80. self._linelen = remainder
  81. elif self._linelen + length > self.wrap:
  82. newline = ('\n' + space) + line
  83. self._linelen = length
  84. else:
  85. newline = line
  86. self._linelen += length
  87. return newline
  88. def _escape_chars(self, text):
  89. text = text.replace('\\', '\\[u005C]'). \
  90. replace('.', '\\[char46]'). \
  91. replace('\'', '\\[u0027]'). \
  92. replace('`', '\\[u0060]'). \
  93. replace('~', '\\[u007E]')
  94. copy = text
  95. for char in copy:
  96. if len(char) != len(char.encode()):
  97. uni = char.encode('unicode_escape') \
  98. .decode()[1:] \
  99. .replace('x', 'u00') \
  100. .upper()
  101. text = text.replace(char, '\\[u' + uni[1:] + ']')
  102. return text
  103. def format_unencoded(self, tokensource, outfile):
  104. self._define_colors(outfile)
  105. outfile.write('.nf\n\\f[CR]\n')
  106. if self.linenos:
  107. self._write_lineno(outfile)
  108. for ttype, value in tokensource:
  109. while ttype not in self.styles:
  110. ttype = ttype.parent
  111. start, end = self.styles[ttype]
  112. for line in value.splitlines(True):
  113. if self.wrap > 0:
  114. line = self._wrap_line(line)
  115. if start and end:
  116. text = self._escape_chars(line.rstrip('\n'))
  117. if text != '':
  118. outfile.write(''.join((start, text, end)))
  119. else:
  120. outfile.write(self._escape_chars(line.rstrip('\n')))
  121. if line.endswith('\n'):
  122. if self.linenos:
  123. self._write_lineno(outfile)
  124. self._linelen = 0
  125. else:
  126. outfile.write('\n')
  127. self._linelen = 0
  128. outfile.write('\n.fi')