cmark
My personal build of CMark ✏️
man.c (5444B)
1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <string.h> 4 #include <assert.h> 5 6 #include "config.h" 7 #include "cmark.h" 8 #include "node.h" 9 #include "buffer.h" 10 #include "utf8.h" 11 #include "render.h" 12 13 #define OUT(s, wrap, escaping) renderer->out(renderer, s, wrap, escaping) 14 #define LIT(s) renderer->out(renderer, s, false, LITERAL) 15 #define CR() renderer->cr(renderer) 16 #define BLANKLINE() renderer->blankline(renderer) 17 #define LIST_NUMBER_SIZE 20 18 19 // Functions to convert cmark_nodes to groff man strings. 20 static void S_outc(cmark_renderer *renderer, cmark_escaping escape, int32_t c, 21 unsigned char nextc) { 22 (void)(nextc); 23 24 if (escape == LITERAL) { 25 cmark_render_code_point(renderer, c); 26 return; 27 } 28 29 switch (c) { 30 case 46: 31 if (renderer->begin_line) { 32 cmark_render_ascii(renderer, "\\&."); 33 } else { 34 cmark_render_code_point(renderer, c); 35 } 36 break; 37 case 39: 38 if (renderer->begin_line) { 39 cmark_render_ascii(renderer, "\\&'"); 40 } else { 41 cmark_render_code_point(renderer, c); 42 } 43 break; 44 case 45: 45 cmark_render_ascii(renderer, "\\-"); 46 break; 47 case 92: 48 cmark_render_ascii(renderer, "\\e"); 49 break; 50 case 8216: // left single quote 51 cmark_render_ascii(renderer, "\\[oq]"); 52 break; 53 case 8217: // right single quote 54 cmark_render_ascii(renderer, "\\[cq]"); 55 break; 56 case 8220: // left double quote 57 cmark_render_ascii(renderer, "\\[lq]"); 58 break; 59 case 8221: // right double quote 60 cmark_render_ascii(renderer, "\\[rq]"); 61 break; 62 case 8212: // em dash 63 cmark_render_ascii(renderer, "\\[em]"); 64 break; 65 case 8211: // en dash 66 cmark_render_ascii(renderer, "\\[en]"); 67 break; 68 default: 69 cmark_render_code_point(renderer, c); 70 } 71 } 72 73 static int S_render_node(cmark_renderer *renderer, cmark_node *node, 74 cmark_event_type ev_type, int options) { 75 cmark_node *tmp; 76 int list_number; 77 bool entering = (ev_type == CMARK_EVENT_ENTER); 78 bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options); 79 80 // avoid unused parameter error: 81 (void)(options); 82 83 switch (node->type) { 84 case CMARK_NODE_DOCUMENT: 85 break; 86 87 case CMARK_NODE_BLOCK_QUOTE: 88 if (entering) { 89 CR(); 90 LIT(".RS"); 91 CR(); 92 } else { 93 CR(); 94 LIT(".RE"); 95 CR(); 96 } 97 break; 98 99 case CMARK_NODE_LIST: 100 break; 101 102 case CMARK_NODE_ITEM: 103 if (entering) { 104 CR(); 105 LIT(".IP "); 106 if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { 107 LIT("\\[bu] 2"); 108 } else { 109 list_number = cmark_node_get_list_start(node->parent); 110 tmp = node; 111 while (tmp->prev) { 112 tmp = tmp->prev; 113 list_number += 1; 114 } 115 char list_number_s[LIST_NUMBER_SIZE]; 116 snprintf(list_number_s, LIST_NUMBER_SIZE, "\"%d.\" 4", list_number); 117 LIT(list_number_s); 118 } 119 CR(); 120 } else { 121 CR(); 122 } 123 break; 124 125 case CMARK_NODE_HEADING: 126 if (entering) { 127 CR(); 128 LIT(cmark_node_get_heading_level(node) == 1 ? ".SH" : ".SS"); 129 CR(); 130 } else { 131 CR(); 132 } 133 break; 134 135 case CMARK_NODE_CODE_BLOCK: 136 CR(); 137 LIT(".IP\n.nf\n\\f[C]\n"); 138 OUT(cmark_node_get_literal(node), false, NORMAL); 139 CR(); 140 LIT("\\f[]\n.fi"); 141 CR(); 142 break; 143 144 case CMARK_NODE_HTML_BLOCK: 145 break; 146 147 case CMARK_NODE_CUSTOM_BLOCK: 148 CR(); 149 OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node), 150 false, LITERAL); 151 CR(); 152 break; 153 154 case CMARK_NODE_THEMATIC_BREAK: 155 CR(); 156 LIT(".PP\n * * * * *"); 157 CR(); 158 break; 159 160 case CMARK_NODE_PARAGRAPH: 161 if (entering) { 162 // no blank line if first paragraph in list: 163 if (node->parent && node->parent->type == CMARK_NODE_ITEM && 164 node->prev == NULL) { 165 // no blank line or .PP 166 } else { 167 CR(); 168 LIT(".PP"); 169 CR(); 170 } 171 } else { 172 CR(); 173 } 174 break; 175 176 case CMARK_NODE_TEXT: 177 OUT(cmark_node_get_literal(node), allow_wrap, NORMAL); 178 break; 179 180 case CMARK_NODE_LINEBREAK: 181 LIT(".PD 0\n.P\n.PD"); 182 CR(); 183 break; 184 185 case CMARK_NODE_SOFTBREAK: 186 if (options & CMARK_OPT_HARDBREAKS) { 187 LIT(".PD 0\n.P\n.PD"); 188 CR(); 189 } else if (renderer->width == 0 && !(CMARK_OPT_NOBREAKS & options)) { 190 CR(); 191 } else { 192 OUT(" ", allow_wrap, LITERAL); 193 } 194 break; 195 196 case CMARK_NODE_CODE: 197 LIT("\\f[C]"); 198 OUT(cmark_node_get_literal(node), allow_wrap, NORMAL); 199 LIT("\\f[]"); 200 break; 201 202 case CMARK_NODE_HTML_INLINE: 203 break; 204 205 case CMARK_NODE_CUSTOM_INLINE: 206 OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node), 207 false, LITERAL); 208 break; 209 210 case CMARK_NODE_STRONG: 211 if (entering) { 212 LIT("\\f[B]"); 213 } else { 214 LIT("\\f[]"); 215 } 216 break; 217 218 case CMARK_NODE_EMPH: 219 if (entering) { 220 LIT("\\f[I]"); 221 } else { 222 LIT("\\f[]"); 223 } 224 break; 225 226 case CMARK_NODE_LINK: 227 if (!entering) { 228 LIT(" ("); 229 OUT(cmark_node_get_url(node), allow_wrap, URL); 230 LIT(")"); 231 } 232 break; 233 234 case CMARK_NODE_IMAGE: 235 if (entering) { 236 LIT("[IMAGE: "); 237 } else { 238 LIT("]"); 239 } 240 break; 241 242 default: 243 assert(false); 244 break; 245 } 246 247 return 1; 248 } 249 250 char *cmark_render_man(cmark_node *root, int options, int width) { 251 return cmark_render(root, options, width, S_outc, S_render_node); 252 }