ggroff.py 7.1 KB

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