Coverage Report

Created: 2026-02-26 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cmark/src/man.c
Line
Count
Source
1
#include <assert.h>
2
#include <stdbool.h>
3
#include <stdio.h>
4
#include <stdlib.h>
5
#include <string.h>
6
7
#include "cmark.h"
8
#include "node.h"
9
#include "buffer.h"
10
#include "utf8.h"
11
#include "render.h"
12
13
394k
#define OUT(s, wrap, escaping) renderer->out(renderer, s, wrap, escaping)
14
3.91M
#define LIT(s) renderer->out(renderer, s, false, LITERAL)
15
5.89M
#define CR() renderer->cr(renderer)
16
#define BLANKLINE() renderer->blankline(renderer)
17
12
#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
37.7M
                   unsigned char nextc) {
22
37.7M
  (void)(nextc);
23
24
37.7M
  if (escape == LITERAL) {
25
0
    cmark_render_code_point(renderer, c);
26
0
    return;
27
0
  }
28
29
37.7M
  switch (c) {
30
10.2k
  case 46:
31
10.2k
    if (renderer->begin_line) {
32
3.73k
      cmark_render_ascii(renderer, "\\&.");
33
6.47k
    } else {
34
6.47k
      cmark_render_code_point(renderer, c);
35
6.47k
    }
36
10.2k
    break;
37
4.38k
  case 39:
38
4.38k
    if (renderer->begin_line) {
39
11
      cmark_render_ascii(renderer, "\\&'");
40
4.37k
    } else {
41
4.37k
      cmark_render_code_point(renderer, c);
42
4.37k
    }
43
4.38k
    break;
44
488k
  case 45:
45
488k
    cmark_render_ascii(renderer, "\\-");
46
488k
    break;
47
42.7k
  case 92:
48
42.7k
    cmark_render_ascii(renderer, "\\e");
49
42.7k
    break;
50
105
  case 8216: // left single quote
51
105
    cmark_render_ascii(renderer, "\\[oq]");
52
105
    break;
53
2.11k
  case 8217: // right single quote
54
2.11k
    cmark_render_ascii(renderer, "\\[cq]");
55
2.11k
    break;
56
756
  case 8220: // left double quote
57
756
    cmark_render_ascii(renderer, "\\[lq]");
58
756
    break;
59
167k
  case 8221: // right double quote
60
167k
    cmark_render_ascii(renderer, "\\[rq]");
61
167k
    break;
62
2.62k
  case 8212: // em dash
63
2.62k
    cmark_render_ascii(renderer, "\\[em]");
64
2.62k
    break;
65
52.5k
  case 8211: // en dash
66
52.5k
    cmark_render_ascii(renderer, "\\[en]");
67
52.5k
    break;
68
36.9M
  default:
69
36.9M
    cmark_render_code_point(renderer, c);
70
37.7M
  }
71
37.7M
}
72
73
static int S_render_node(cmark_renderer *renderer, cmark_node *node,
74
5.42M
                         cmark_event_type ev_type, int options) {
75
5.42M
  cmark_node *tmp;
76
5.42M
  int list_number;
77
5.42M
  bool entering = (ev_type == CMARK_EVENT_ENTER);
78
5.42M
  bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options);
79
5.42M
  struct block_number *new_block_number;
80
5.42M
  cmark_mem *allocator = cmark_get_default_mem_allocator();
81
82
  // avoid unused parameter error:
83
5.42M
  (void)(options);
84
85
  // indent inside nested lists
86
5.42M
  if (renderer->block_number_in_list_item &&
87
1.49M
      node->type < CMARK_NODE_FIRST_INLINE) {
88
1.48M
    if (entering) {
89
431k
      renderer->block_number_in_list_item->number += 1;
90
431k
      if (renderer->block_number_in_list_item->number == 2) {
91
214k
        CR();
92
214k
        LIT(".RS"); // indent
93
214k
        CR();
94
214k
      }
95
431k
    }
96
1.48M
  }
97
98
5.42M
  switch (node->type) {
99
352
  case CMARK_NODE_DOCUMENT:
100
352
    break;
101
102
1.15M
  case CMARK_NODE_BLOCK_QUOTE:
103
1.15M
    if (entering) {
104
576k
      CR();
105
576k
      LIT(".RS");
106
576k
      CR();
107
576k
    } else {
108
576k
      CR();
109
576k
      LIT(".RE");
110
576k
      CR();
111
576k
    }
112
1.15M
    break;
113
114
1.67M
  case CMARK_NODE_LIST:
115
1.67M
    break;
116
117
1.67M
  case CMARK_NODE_ITEM:
118
1.67M
    if (entering) {
119
838k
      new_block_number = allocator->calloc(1, sizeof(struct block_number));
120
838k
      new_block_number->number = 0;
121
838k
      new_block_number->parent = renderer->block_number_in_list_item;
122
838k
      renderer->block_number_in_list_item = new_block_number;
123
838k
      CR();
124
838k
      LIT(".IP ");
125
838k
      if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) {
126
838k
        LIT("\\[bu] 2");
127
838k
      } else {
128
12
        list_number = cmark_node_get_list_start(node->parent);
129
12
        tmp = node;
130
13
        while (tmp->prev) {
131
1
          tmp = tmp->prev;
132
1
          list_number += 1;
133
1
        }
134
12
        char list_number_s[LIST_NUMBER_SIZE];
135
12
        snprintf(list_number_s, LIST_NUMBER_SIZE, "\"%d.\" 4", list_number);
136
12
        LIT(list_number_s);
137
12
      }
138
838k
      CR();
139
838k
    } else {
140
838k
      if (renderer->block_number_in_list_item) {
141
838k
        if (renderer->block_number_in_list_item->number >= 2) {
142
214k
          CR();
143
214k
          LIT(".RE"); // de-indent
144
214k
        }
145
838k
        new_block_number = renderer->block_number_in_list_item;
146
838k
        renderer->block_number_in_list_item =
147
838k
          renderer->block_number_in_list_item->parent;
148
838k
        allocator->free(new_block_number);
149
838k
      }
150
838k
      CR();
151
838k
    }
152
1.67M
    break;
153
154
39.2k
  case CMARK_NODE_HEADING:
155
39.2k
    if (entering) {
156
19.6k
      CR();
157
19.6k
      LIT(cmark_node_get_heading_level(node) == 1 ? ".SH" : ".SS");
158
19.6k
      CR();
159
19.6k
    } else {
160
19.6k
      CR();
161
19.6k
    }
162
39.2k
    break;
163
164
87.5k
  case CMARK_NODE_CODE_BLOCK:
165
87.5k
    CR();
166
87.5k
    LIT(".IP\n.nf\n\\f[C]\n");
167
87.5k
    OUT(cmark_node_get_literal(node), false, NORMAL);
168
87.5k
    CR();
169
87.5k
    LIT("\\f[]\n.fi");
170
87.5k
    CR();
171
87.5k
    break;
172
173
5.85k
  case CMARK_NODE_HTML_BLOCK:
174
5.85k
    break;
175
176
0
  case CMARK_NODE_CUSTOM_BLOCK:
177
0
    CR();
178
0
    OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node),
179
0
        false, LITERAL);
180
0
    CR();
181
0
    break;
182
183
5
  case CMARK_NODE_THEMATIC_BREAK:
184
5
    CR();
185
5
    LIT(".PP\n  *  *  *  *  *");
186
5
    CR();
187
5
    break;
188
189
71.3k
  case CMARK_NODE_PARAGRAPH:
190
71.3k
    if (entering) {
191
      // no blank line if first paragraph in list:
192
35.6k
      if (node->parent && node->parent->type == CMARK_NODE_ITEM &&
193
2.25k
          node->prev == NULL) {
194
        // no blank line or .PP
195
34.1k
      } else {
196
34.1k
        CR();
197
34.1k
        LIT(".PP");
198
34.1k
        CR();
199
34.1k
      }
200
35.6k
    } else {
201
35.6k
      CR();
202
35.6k
    }
203
71.3k
    break;
204
205
193k
  case CMARK_NODE_TEXT:
206
193k
    OUT(cmark_node_get_literal(node), allow_wrap, NORMAL);
207
193k
    break;
208
209
251
  case CMARK_NODE_LINEBREAK:
210
251
    LIT(".PD 0\n.P\n.PD");
211
251
    CR();
212
251
    break;
213
214
105k
  case CMARK_NODE_SOFTBREAK:
215
105k
    if (options & CMARK_OPT_HARDBREAKS) {
216
10.5k
      LIT(".PD 0\n.P\n.PD");
217
10.5k
      CR();
218
95.4k
    } else if (renderer->width == 0 && !(CMARK_OPT_NOBREAKS & options)) {
219
39
      CR();
220
95.4k
    } else {
221
95.4k
      OUT(" ", allow_wrap, LITERAL);
222
95.4k
    }
223
105k
    break;
224
225
13.1k
  case CMARK_NODE_CODE:
226
13.1k
    LIT("\\f[C]");
227
13.1k
    OUT(cmark_node_get_literal(node), allow_wrap, NORMAL);
228
13.1k
    LIT("\\f[]");
229
13.1k
    break;
230
231
16.4k
  case CMARK_NODE_HTML_INLINE:
232
16.4k
    break;
233
234
0
  case CMARK_NODE_CUSTOM_INLINE:
235
0
    OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node),
236
0
        false, LITERAL);
237
0
    break;
238
239
365k
  case CMARK_NODE_STRONG:
240
365k
    if (entering) {
241
182k
      LIT("\\f[B]");
242
182k
    } else {
243
182k
      LIT("\\f[]");
244
182k
    }
245
365k
    break;
246
247
13.5k
  case CMARK_NODE_EMPH:
248
13.5k
    if (entering) {
249
6.78k
      LIT("\\f[I]");
250
6.78k
    } else {
251
6.78k
      LIT("\\f[]");
252
6.78k
    }
253
13.5k
    break;
254
255
9.37k
  case CMARK_NODE_LINK:
256
9.37k
    if (!entering) {
257
4.68k
      LIT(" (");
258
4.68k
      OUT(cmark_node_get_url(node), allow_wrap, URL);
259
4.68k
      LIT(")");
260
4.68k
    }
261
9.37k
    break;
262
263
88
  case CMARK_NODE_IMAGE:
264
88
    if (entering) {
265
44
      LIT("[IMAGE: ");
266
44
    } else {
267
44
      LIT("]");
268
44
    }
269
88
    break;
270
271
0
  default:
272
0
    assert(false);
273
0
    break;
274
5.42M
  }
275
276
5.42M
  return 1;
277
5.42M
}
278
279
176
char *cmark_render_man(cmark_node *root, int options, int width) {
280
176
  return cmark_render(root, options, width, S_outc, S_render_node);
281
176
}