ggroff.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. import sys
  2. import os
  3. import re
  4. __all__ = ["Formatter"]
  5. try:
  6. version = os.environ['VERSION_NUMBER']
  7. except:
  8. version = ""
  9. styles = {
  10. 'b': "\\fB@\\fR",
  11. 'i': "\\fI@\\fR",
  12. 'em': "\\fI@\\fR",
  13. 'code': "\\fC@\\fR",
  14. 'span': "\\fC@\\fR",
  15. 'sup': "\\u@\\d",
  16. 'hr': ""
  17. }
  18. formats = {
  19. 'br': "\n.br\n",
  20. 'h2': "\n.SH @",
  21. 'h3': "\n.SS @",
  22. 'h4': "\n.SS @",
  23. 'dt': ("\n.IP \"@\" 4m", 'no_nl'),
  24. 'dd': "\n.br\n@",
  25. 'ul': ("\n.RS 4n\n@\n.RE\n", 'in_ul'),
  26. 'menu': ("\n.RS 4n\n@\n.RE\n", 'in_ul'),
  27. 'dir': ("\n.RS 4n\n@\n.RE\n", 'in_ul'),
  28. 'ol': ("\n.IP\n@\n.PP\n", 'index'),
  29. 'p': "\n.PP\n@",
  30. 'pre': ("\n.br\n.nf\n\\fC\n@\n\\fR\n.fi\n", 'preformat')
  31. }
  32. formats.update(styles)
  33. def is_string(x):
  34. return isinstance(x, str)
  35. def is_tuple(x):
  36. return isinstance(x, tuple)
  37. def is_list(x):
  38. return isinstance(x, list)
  39. def is_blank(s):
  40. return is_string(s) and s.strip() == ""
  41. def clean(content):
  42. return [item for item in content if not is_blank(item)]
  43. class Formatter:
  44. def __init__(self, filename, stream=sys.stdout):
  45. self.stream = stream
  46. self.style = dict(preformat=False,
  47. in_ul=False,
  48. no_nl=False,
  49. in_table=False,
  50. in_tr=False,
  51. index=[])
  52. self.stack = []
  53. self.strip_re = re.compile("^[ \t]+")
  54. self.filename = filename
  55. self.at_bol = True
  56. def warning(self, msg):
  57. sys.stderr.write(msg + '\n')
  58. def set(self, var, val):
  59. self.style[var] = val
  60. def get(self, var):
  61. return self.style[var]
  62. def push(self, **kwargs):
  63. self.stack.append(self.style.copy())
  64. self.style.update(**kwargs)
  65. def pop(self):
  66. self.style = self.stack.pop()
  67. def show(self, s):
  68. self.stream.write(s)
  69. if s != '':
  70. self.at_bol = s.endswith('\n')
  71. def pp_with(self, content, var, val):
  72. self.push()
  73. self.set(var, val)
  74. self.pp(content)
  75. self.pop()
  76. def fmt(self, format, content, var=None):
  77. # String.partition is only in 2.5+
  78. # (pre,sep,post) = format.partition("@")
  79. if self.get('no_nl') and '\n' in format:
  80. self.warning("can't handle line breaks in <dt>...</dt>")
  81. format = "@"
  82. f = format.split('@', 1)
  83. pre = f[0]
  84. if len(f) > 1:
  85. sep = '@'
  86. post = f[1]
  87. else:
  88. sep = ''
  89. post = ''
  90. if pre != "":
  91. self.show(pre)
  92. if sep != "":
  93. if var:
  94. if var == 'index':
  95. val = self.get('index') + [0]
  96. else:
  97. val = True
  98. self.pp_with(content, var, val)
  99. else:
  100. self.pp(content)
  101. if post != "":
  102. self.show(post)
  103. def pp_li(self, content):
  104. if self.get('in_ul'):
  105. self.fmt("\n.IP \(bu 4n\n@", content)
  106. else:
  107. idx = self.get('index')
  108. idx[-1] += 1
  109. sec = ".".join(map(str, idx))
  110. self.show("\n.IP \\fB%s\\fR\n" % sec)
  111. self.set('index', idx)
  112. self.pp(content)
  113. def pp_title(self):
  114. self.show("\n.TH " +
  115. os.path.basename(self.filename).replace(".html", "") +
  116. " 1 \"\" \"GRASS " +
  117. version +
  118. "\" \"Grass User's Manual\"")
  119. def pp_tr(self, content):
  120. content = clean(content)
  121. self.push(in_tr=True)
  122. col = 0
  123. for item in content:
  124. if not is_tuple(item):
  125. self.warning("invalid item in table row: %s" % str(item))
  126. continue
  127. (tag, attrs, body) = item
  128. if tag not in ['td', 'th']:
  129. self.warning("invalid tag in table row: %s" % tag)
  130. continue
  131. if col > 0:
  132. self.show("\t \t")
  133. self.show("T{\n")
  134. self.pp(body)
  135. self.show("\nT}")
  136. col += 1
  137. self.show("\n")
  138. self.pop()
  139. def pp_tbody(self, content):
  140. for item in content:
  141. if is_tuple(item):
  142. (tag, attrs, body) = item
  143. if tag in ['thead', 'tbody', 'tfoot']:
  144. self.pp_tbody(body)
  145. elif tag == 'tr':
  146. self.pp_tr(body)
  147. self.show(".sp 1\n")
  148. def count_cols(self, content):
  149. cols = 0
  150. for item in content:
  151. n = 0
  152. if is_blank(item):
  153. pass
  154. elif is_tuple(item):
  155. (tag, attrs, body) = item
  156. if tag in ['thead', 'tbody', 'tfoot']:
  157. n = self.count_cols(body)
  158. elif tag == 'tr':
  159. n = len(clean(body))
  160. cols = max(cols, n)
  161. else:
  162. self.warning("invalid item in table: %s" % str(item))
  163. return cols
  164. def pp_table(self, content):
  165. cols = self.count_cols(content)
  166. if cols == 0:
  167. return
  168. self.show("\n.TS\nexpand;\n")
  169. self.show(" lw1 ".join(["lw60" for i in range(cols)]) + ".\n")
  170. self.pp_tbody(content)
  171. self.show("\n.TE\n")
  172. def pp_tag(self, tag, content):
  173. if self.get('in_tr') and tag not in styles:
  174. self.pp(content)
  175. elif tag in formats:
  176. spec = formats[tag]
  177. if is_string(spec):
  178. self.fmt(spec, content)
  179. else:
  180. (fmt, var) = spec
  181. self.fmt(fmt, content, var)
  182. elif tag == 'table':
  183. if self.get('in_table'):
  184. self.warning("cannot handle nested tables")
  185. return
  186. self.push(in_table=True)
  187. self.pp_table(content)
  188. self.pop()
  189. elif tag == 'li':
  190. self.pp_li(content)
  191. elif tag == 'title':
  192. self.pp_title()
  193. else:
  194. self.pp(content)
  195. def pp_string(self, content):
  196. if content == "":
  197. return
  198. s = content
  199. if self.get('no_nl'):
  200. s = s.replace("\n", " ")
  201. s = s.replace("\\", "\\(rs")
  202. s = s.replace("'", "\\(cq")
  203. s = s.replace("\"", "\\(dq")
  204. s = s.replace("`", "\\(ga")
  205. s = s.replace("-", "\\-")
  206. if self.at_bol and s[0] in [".", "'"]:
  207. s = "\\&" + s
  208. self.show(s)
  209. def pp_text(self, content):
  210. if content == "":
  211. return
  212. lines = content.splitlines(True)
  213. if len(lines) != 1:
  214. for line in lines:
  215. self.pp_text(line)
  216. return
  217. else:
  218. content = lines[0]
  219. if self.at_bol and not self.get('preformat'):
  220. content = self.strip_re.sub('', content)
  221. self.pp_string(content)
  222. def pp_list(self, content):
  223. for item in content:
  224. self.pp(item)
  225. def pp(self, content):
  226. if is_list(content):
  227. self.pp_list(content)
  228. elif is_tuple(content):
  229. (tag, attrs, body) = content
  230. self.pp_tag(tag, body)
  231. elif is_string(content):
  232. self.pp_text(content)