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