aboutsummaryrefslogtreecommitdiff
path: root/src/man.c
blob: aa81b2bf423af461ab831a1bd52ac782d1622216 (plain)
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <assert.h>
  5. #include "config.h"
  6. #include "cmark.h"
  7. #include "node.h"
  8. #include "buffer.h"
  9. // Functions to convert cmark_nodes to groff man strings.
  10. static void escape_man(cmark_strbuf *dest, const unsigned char *source, int length)
  11. {
  12. int i;
  13. unsigned char c;
  14. bool beginLine = true;
  15. for (i = 0; i < length; i++) {
  16. c = source[i];
  17. if (c == '.' && beginLine) {
  18. cmark_strbuf_puts(dest, "\\&.");
  19. } else if (c == '\'' && beginLine) {
  20. cmark_strbuf_puts(dest, "\\&'");
  21. } else if (c == '-') {
  22. cmark_strbuf_puts(dest, "\\-");
  23. } else if (c == '\\') {
  24. cmark_strbuf_puts(dest, "\\e");
  25. } else {
  26. cmark_strbuf_putc(dest, source[i]);
  27. }
  28. beginLine = (c == '\n');
  29. }
  30. }
  31. static inline void cr(cmark_strbuf *man)
  32. {
  33. if (man->size && man->ptr[man->size - 1] != '\n')
  34. cmark_strbuf_putc(man, '\n');
  35. }
  36. struct render_state {
  37. cmark_strbuf* man;
  38. cmark_node *plain;
  39. };
  40. static int
  41. S_render_node(cmark_node *node, cmark_event_type ev_type,
  42. struct render_state *state)
  43. {
  44. cmark_node *tmp;
  45. cmark_strbuf *man = state->man;
  46. int list_number;
  47. bool entering = (ev_type == CMARK_EVENT_ENTER);
  48. if (state->plain == node) { // back at original node
  49. state->plain = NULL;
  50. }
  51. if (state->plain != NULL) {
  52. switch(node->type) {
  53. case CMARK_NODE_TEXT:
  54. case CMARK_NODE_CODE:
  55. escape_man(man, node->as.literal.data,
  56. node->as.literal.len);
  57. break;
  58. case CMARK_NODE_LINEBREAK:
  59. case CMARK_NODE_SOFTBREAK:
  60. cmark_strbuf_putc(man, ' ');
  61. break;
  62. default:
  63. break;
  64. }
  65. return 1;
  66. }
  67. switch (node->type) {
  68. case CMARK_NODE_DOCUMENT:
  69. break;
  70. case CMARK_NODE_BLOCK_QUOTE:
  71. if (entering) {
  72. cr(man);
  73. cmark_strbuf_puts(man, ".RS");
  74. cr(man);
  75. } else {
  76. cr(man);
  77. cmark_strbuf_puts(man, ".RE");
  78. cr(man);
  79. }
  80. break;
  81. case CMARK_NODE_LIST:
  82. break;
  83. case CMARK_NODE_ITEM:
  84. if (entering) {
  85. cr(man);
  86. cmark_strbuf_puts(man, ".IP ");
  87. if (cmark_node_get_list_type(node->parent) ==
  88. CMARK_BULLET_LIST) {
  89. cmark_strbuf_puts(man, "\\[bu] 2");
  90. } else {
  91. list_number = cmark_node_get_list_start(node->parent);
  92. tmp = node;
  93. while (tmp->prev) {
  94. tmp = tmp->prev;
  95. list_number += 1;
  96. }
  97. cmark_strbuf_printf(man, "\"%d.\" 4", list_number);
  98. }
  99. cr(man);
  100. } else {
  101. cr(man);
  102. }
  103. break;
  104. case CMARK_NODE_HEADER:
  105. if (entering) {
  106. cr(man);
  107. cmark_strbuf_puts(man,
  108. cmark_node_get_header_level(node) == 1 ?
  109. ".SH" : ".SS");
  110. cr(man);
  111. } else {
  112. cr(man);
  113. }
  114. break;
  115. case CMARK_NODE_CODE_BLOCK:
  116. cr(man);
  117. cmark_strbuf_puts(man, ".IP\n.nf\n\\f[C]\n");
  118. escape_man(man, node->as.code.literal.data,
  119. node->as.code.literal.len);
  120. cr(man);
  121. cmark_strbuf_puts(man, "\\f[]\n.fi");
  122. cr(man);
  123. break;
  124. case CMARK_NODE_HTML:
  125. break;
  126. case CMARK_NODE_HRULE:
  127. cr(man);
  128. cmark_strbuf_puts(man, ".PP\n * * * * *");
  129. cr(man);
  130. break;
  131. case CMARK_NODE_PARAGRAPH:
  132. if (entering) {
  133. // no blank line if first paragraph in list:
  134. if (node->parent &&
  135. node->parent->type == CMARK_NODE_ITEM &&
  136. node->prev == NULL) {
  137. // no blank line or .PP
  138. } else {
  139. cr(man);
  140. cmark_strbuf_puts(man, ".PP\n");
  141. }
  142. } else {
  143. cr(man);
  144. }
  145. break;
  146. case CMARK_NODE_TEXT:
  147. escape_man(man, node->as.literal.data,
  148. node->as.literal.len);
  149. break;
  150. case CMARK_NODE_LINEBREAK:
  151. cmark_strbuf_puts(man, ".PD 0\n.P\n.PD");
  152. cr(man);
  153. break;
  154. case CMARK_NODE_SOFTBREAK:
  155. cmark_strbuf_putc(man, '\n');
  156. break;
  157. case CMARK_NODE_CODE:
  158. cmark_strbuf_puts(man, "\\f[C]");
  159. escape_man(man, node->as.literal.data, node->as.literal.len);
  160. cmark_strbuf_puts(man, "\\f[]");
  161. break;
  162. case CMARK_NODE_INLINE_HTML:
  163. break;
  164. case CMARK_NODE_STRONG:
  165. if (entering) {
  166. cmark_strbuf_puts(man, "\\f[B]");
  167. } else {
  168. cmark_strbuf_puts(man, "\\f[]");
  169. }
  170. break;
  171. case CMARK_NODE_EMPH:
  172. if (entering) {
  173. cmark_strbuf_puts(man, "\\f[I]");
  174. } else {
  175. cmark_strbuf_puts(man, "\\f[]");
  176. }
  177. break;
  178. case CMARK_NODE_LINK:
  179. if (!entering) {
  180. cmark_strbuf_printf(man, " (%s)",
  181. cmark_node_get_url(node));
  182. }
  183. break;
  184. case CMARK_NODE_IMAGE:
  185. if (entering) {
  186. cmark_strbuf_puts(man, "[IMAGE: ");
  187. state->plain = node;
  188. } else {
  189. cmark_strbuf_puts(man, "]");
  190. }
  191. break;
  192. default:
  193. assert(false);
  194. break;
  195. }
  196. // cmark_strbuf_putc(man, 'x');
  197. return 1;
  198. }
  199. char *cmark_render_man(cmark_node *root, long options)
  200. {
  201. char *result;
  202. cmark_strbuf man = GH_BUF_INIT;
  203. struct render_state state = { &man, NULL };
  204. cmark_node *cur;
  205. cmark_event_type ev_type;
  206. cmark_iter *iter = cmark_iter_new(root);
  207. if (options == 0) options = 0; // avoid warning about unused parameters
  208. while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
  209. cur = cmark_iter_get_node(iter);
  210. S_render_node(cur, ev_type, &state);
  211. }
  212. result = (char *)cmark_strbuf_detach(&man);
  213. cmark_iter_free(iter);
  214. cmark_strbuf_free(&man);
  215. return result;
  216. }