File Coverage

include/eshu_pod.h
Criterion Covered Total %
statement 74 74 100.0
branch 38 48 79.1
condition n/a
subroutine n/a
pod n/a
total 112 122 91.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 verbatim_base; /* base indent of current block */
25             int over_depth; /* =over nesting depth */
26             eshu_config_t cfg;
27             } eshu_pod_ctx_t;
28              
29 47           static void eshu_pod_ctx_init(eshu_pod_ctx_t *ctx, const eshu_config_t *cfg) {
30 47           ctx->in_verbatim = 0;
31 47           ctx->verbatim_base = 0;
32 47           ctx->over_depth = 0;
33 47           ctx->cfg = *cfg;
34 47           }
35              
36             /* ══════════════════════════════════════════════════════════════════
37             * Detect POD directive lines (=head1, =over, =item, =cut, etc.)
38             * These are lines starting with '=' followed by a letter.
39             * ══════════════════════════════════════════════════════════════════ */
40              
41 715           static int eshu_pod_is_directive(const char *p) {
42 715 100         return (*p == '=' && isalpha((unsigned char)p[1]));
    50          
43             }
44              
45             /* Case-insensitive prefix match for directive names */
46 447           static int eshu_pod_directive_is(const char *p, const char *name, int nlen) {
47             int i;
48 447 50         if (*p != '=') return 0;
49 447           p++;
50 569 100         for (i = 0; i < nlen; i++) {
51 539 50         if (!p[i]) return 0;
52 539 100         if (tolower((unsigned char)p[i]) != name[i]) return 0;
53             }
54             /* must be followed by space, digit, or end of meaningful chars */
55 30 50         if (p[nlen] && isalpha((unsigned char)p[nlen])) return 0;
    50          
56 30           return 1;
57             }
58              
59             /* ══════════════════════════════════════════════════════════════════
60             * Find the minimum leading whitespace in a block of verbatim lines.
61             * Used for normalization: we strip this common prefix and re-indent.
62             * ══════════════════════════════════════════════════════════════════ */
63              
64 277           static int eshu_pod_measure_indent(const char *line_start, const char *eol) {
65 277           int n = 0;
66 277           const char *p = line_start;
67 1171 50         while (p < eol && (*p == ' ' || *p == '\t')) {
    100          
    100          
68 894 100         if (*p == '\t')
69 191           n += 4; /* count tab as 4 spaces for measurement */
70             else
71 703           n++;
72 894           p++;
73             }
74 277           return n;
75             }
76              
77             /* ══════════════════════════════════════════════════════════════════
78             * Process a single POD line
79             * ══════════════════════════════════════════════════════════════════ */
80              
81 1137           static void eshu_pod_process_line(eshu_pod_ctx_t *ctx, eshu_buf_t *out,
82             const char *line_start, const char *eol) {
83 1137           const char *content = eshu_skip_leading_ws(line_start);
84 1137           int content_len = (int)(eol - content);
85 1137           int has_leading_ws = (content > line_start);
86              
87             /* ── Blank line ── */
88 1137 100         if (content >= eol) {
89 422           ctx->in_verbatim = 0;
90 422           eshu_buf_putc(out, '\n');
91 422           return;
92             }
93              
94             /* ── Directive line (=something) — always column 0 ── */
95 715 100         if (eshu_pod_is_directive(content)) {
96 231           ctx->in_verbatim = 0;
97              
98             /* Track =over/=back depth */
99 231 100         if (eshu_pod_directive_is(content, "over", 4)) {
100 15           ctx->over_depth++;
101 216 100         } else if (eshu_pod_directive_is(content, "back", 4)) {
102 15           ctx->over_depth--;
103 15 50         if (ctx->over_depth < 0) ctx->over_depth = 0;
104             }
105              
106             /* Emit at column 0 (strip any accidental leading whitespace) */
107 231           eshu_buf_write_trimmed(out, content, content_len);
108 231           eshu_buf_putc(out, '\n');
109 231           return;
110             }
111              
112             /* ── Verbatim/code block: line starts with whitespace ── */
113 484 100         if (has_leading_ws) {
114 277           int line_indent = eshu_pod_measure_indent(line_start, eol);
115              
116 277 100         if (!ctx->in_verbatim) {
117             /* First line of a new verbatim block — record base indent */
118 114           ctx->in_verbatim = 1;
119 114           ctx->verbatim_base = line_indent;
120             }
121              
122             /* Compute relative indent beyond the block's base */
123 277           int extra = line_indent - ctx->verbatim_base;
124 277 50         if (extra < 0) extra = 0;
125              
126             /* Emit: one base indent level + extra relative whitespace */
127 277           eshu_emit_indent(out, 1, &ctx->cfg);
128             {
129             int i;
130 635 100         for (i = 0; i < extra; i++)
131 358           eshu_buf_putc(out, ' ');
132             }
133 277           eshu_buf_write_trimmed(out, content, content_len);
134 277           eshu_buf_putc(out, '\n');
135 277           return;
136             }
137              
138             /* ── Normal text paragraph: stays at column 0 ── */
139 207           ctx->in_verbatim = 0;
140 207           eshu_buf_write_trimmed(out, content, content_len);
141 207           eshu_buf_putc(out, '\n');
142             }
143              
144             /* ══════════════════════════════════════════════════════════════════
145             * Public API — indent a POD string
146             * ══════════════════════════════════════════════════════════════════ */
147              
148 47           static char * eshu_indent_pod(const char *src, size_t src_len,
149             const eshu_config_t *cfg, size_t *out_len) {
150             eshu_pod_ctx_t ctx;
151             eshu_buf_t out;
152 47           const char *p = src;
153 47           const char *end = src + src_len;
154             char *result;
155              
156 47           eshu_pod_ctx_init(&ctx, cfg);
157 47           eshu_buf_init(&out, src_len + 256);
158              
159 1184 100         while (p < end) {
160 1137           const char *eol = eshu_find_eol(p);
161 1137           eshu_pod_process_line(&ctx, &out, p, eol);
162 1137 50         p = (*eol == '\n') ? eol + 1 : eol;
163 1137 50         if (p > end) p = end;
164             }
165              
166             /* NUL-terminate */
167 47           eshu_buf_putc(&out, '\0');
168 47           out.len--;
169              
170 47           *out_len = out.len;
171 47           result = out.data;
172 47           return result;
173             }
174              
175             #endif /* ESHU_POD_H */