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