File Coverage

include/eshu_pod.h
Criterion Covered Total %
statement 57 66 86.3
branch 26 42 61.9
condition n/a
subroutine n/a
pod n/a
total 83 108 76.8


line stmt bran cond sub pod time code
1             /*
2             * eshu_pod.h — POD (Plain Old Documentation) indentation scanner
3             *
4             * Normalizes indentation within POD sections:
5             * - Directives (=head1, =over, =item, etc.) stay at column 0
6             * - Text paragraphs stay at column 0
7             * - Verbatim/code blocks (lines starting with whitespace) are
8             * re-indented to a consistent level (one indent unit by default)
9             * - =over/=back nesting is NOT tracked for text indent (that's
10             * a formatter's job) but IS respected for code block context
11             */
12              
13             #ifndef ESHU_POD_H
14             #define ESHU_POD_H
15              
16             #include "eshu.h"
17              
18             /* ══════════════════════════════════════════════════════════════════
19             * Scanner context — persists across lines
20             * ══════════════════════════════════════════════════════════════════ */
21              
22             typedef struct {
23             int in_verbatim; /* currently in a code block? */
24             int over_depth; /* =over nesting depth */
25             eshu_config_t cfg;
26             } eshu_pod_ctx_t;
27              
28 39           static void eshu_pod_ctx_init(eshu_pod_ctx_t *ctx, const eshu_config_t *cfg) {
29 39           ctx->in_verbatim = 0;
30 39           ctx->over_depth = 0;
31 39           ctx->cfg = *cfg;
32 39           }
33              
34             /* ══════════════════════════════════════════════════════════════════
35             * Detect POD directive lines (=head1, =over, =item, =cut, etc.)
36             * These are lines starting with '=' followed by a letter.
37             * ══════════════════════════════════════════════════════════════════ */
38              
39 658           static int eshu_pod_is_directive(const char *p) {
40 658 100         return (*p == '=' && isalpha((unsigned char)p[1]));
    50          
41             }
42              
43             /* Case-insensitive prefix match for directive names */
44 417           static int eshu_pod_directive_is(const char *p, const char *name, int nlen) {
45             int i;
46 417 50         if (*p != '=') return 0;
47 417           p++;
48 539 100         for (i = 0; i < nlen; i++) {
49 509 50         if (!p[i]) return 0;
50 509 100         if (tolower((unsigned char)p[i]) != name[i]) return 0;
51             }
52             /* must be followed by space, digit, or end of meaningful chars */
53 30 50         if (p[nlen] && isalpha((unsigned char)p[nlen])) return 0;
    50          
54 30           return 1;
55             }
56              
57             /* ══════════════════════════════════════════════════════════════════
58             * Find the minimum leading whitespace in a block of verbatim lines.
59             * Used for normalization: we strip this common prefix and re-indent.
60             * ══════════════════════════════════════════════════════════════════ */
61              
62 0           static int eshu_pod_measure_indent(const char *line_start, const char *eol) {
63 0           int n = 0;
64 0           const char *p = line_start;
65 0 0         while (p < eol && (*p == ' ' || *p == '\t')) {
    0          
    0          
66 0 0         if (*p == '\t')
67 0           n += 4; /* count tab as 4 spaces for measurement */
68             else
69 0           n++;
70 0           p++;
71             }
72 0           return n;
73             }
74              
75             /* ══════════════════════════════════════════════════════════════════
76             * Process a single POD line
77             * ══════════════════════════════════════════════════════════════════ */
78              
79 1059           static void eshu_pod_process_line(eshu_pod_ctx_t *ctx, eshu_buf_t *out,
80             const char *line_start, const char *eol) {
81 1059           const char *content = eshu_skip_leading_ws(line_start);
82 1059           int content_len = (int)(eol - content);
83 1059           int has_leading_ws = (content > line_start);
84              
85             /* ── Blank line ── */
86 1059 100         if (content >= eol) {
87 401           ctx->in_verbatim = 0;
88 401           eshu_buf_putc(out, '\n');
89 401           return;
90             }
91              
92             /* ── Directive line (=something) — always column 0 ── */
93 658 100         if (eshu_pod_is_directive(content)) {
94 216           ctx->in_verbatim = 0;
95              
96             /* Track =over/=back depth */
97 216 100         if (eshu_pod_directive_is(content, "over", 4)) {
98 15           ctx->over_depth++;
99 201 100         } else if (eshu_pod_directive_is(content, "back", 4)) {
100 15           ctx->over_depth--;
101 15 50         if (ctx->over_depth < 0) ctx->over_depth = 0;
102             }
103              
104             /* Emit at column 0 (strip any accidental leading whitespace) */
105 216           eshu_buf_write_trimmed(out, content, content_len);
106 216           eshu_buf_putc(out, '\n');
107 216           return;
108             }
109              
110             /* ── Verbatim/code block: line starts with whitespace ── */
111 442 100         if (has_leading_ws) {
112 239           ctx->in_verbatim = 1;
113              
114             /* Remove original leading whitespace, apply one indent level */
115 239           eshu_emit_indent(out, 1, &ctx->cfg);
116 239           eshu_buf_write_trimmed(out, content, content_len);
117 239           eshu_buf_putc(out, '\n');
118 239           return;
119             }
120              
121             /* ── Normal text paragraph: stays at column 0 ── */
122 203           ctx->in_verbatim = 0;
123 203           eshu_buf_write_trimmed(out, content, content_len);
124 203           eshu_buf_putc(out, '\n');
125             }
126              
127             /* ══════════════════════════════════════════════════════════════════
128             * Public API — indent a POD string
129             * ══════════════════════════════════════════════════════════════════ */
130              
131 39           static char * eshu_indent_pod(const char *src, size_t src_len,
132             const eshu_config_t *cfg, size_t *out_len) {
133             eshu_pod_ctx_t ctx;
134             eshu_buf_t out;
135 39           const char *p = src;
136 39           const char *end = src + src_len;
137             char *result;
138              
139 39           eshu_pod_ctx_init(&ctx, cfg);
140 39           eshu_buf_init(&out, src_len + 256);
141              
142 1098 100         while (p < end) {
143 1059           const char *eol = eshu_find_eol(p);
144 1059           eshu_pod_process_line(&ctx, &out, p, eol);
145 1059 50         p = (*eol == '\n') ? eol + 1 : eol;
146 1059 50         if (p > end) p = end;
147             }
148              
149             /* NUL-terminate */
150 39           eshu_buf_putc(&out, '\0');
151 39           out.len--;
152              
153 39           *out_len = out.len;
154 39           result = out.data;
155 39           return result;
156             }
157              
158             #endif /* ESHU_POD_H */