| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
/* |
|
2
|
|
|
|
|
|
|
* eshu_css.h — CSS indentation scanner |
|
3
|
|
|
|
|
|
|
* |
|
4
|
|
|
|
|
|
|
* Brace-based nesting like C, but with CSS-specific quoting, |
|
5
|
|
|
|
|
|
|
* comment syntax (no // line comments), and url() skipping. |
|
6
|
|
|
|
|
|
|
*/ |
|
7
|
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
#ifndef ESHU_CSS_H |
|
9
|
|
|
|
|
|
|
#define ESHU_CSS_H |
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
#include "eshu.h" |
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
/* ══════════════════════════════════════════════════════════════════ |
|
14
|
|
|
|
|
|
|
* Scanner context |
|
15
|
|
|
|
|
|
|
* ══════════════════════════════════════════════════════════════════ */ |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
typedef struct { |
|
18
|
|
|
|
|
|
|
int depth; |
|
19
|
|
|
|
|
|
|
enum eshu_state state; |
|
20
|
|
|
|
|
|
|
eshu_config_t cfg; |
|
21
|
|
|
|
|
|
|
} eshu_css_ctx_t; |
|
22
|
|
|
|
|
|
|
|
|
23
|
31
|
|
|
|
|
|
static void eshu_css_ctx_init(eshu_css_ctx_t *ctx, const eshu_config_t *cfg) { |
|
24
|
31
|
|
|
|
|
|
ctx->depth = 0; |
|
25
|
31
|
|
|
|
|
|
ctx->state = ESHU_CODE; |
|
26
|
31
|
|
|
|
|
|
ctx->cfg = *cfg; |
|
27
|
31
|
|
|
|
|
|
} |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
/* ══════════════════════════════════════════════════════════════════ |
|
30
|
|
|
|
|
|
|
* Scan a line to compute depth changes |
|
31
|
|
|
|
|
|
|
* |
|
32
|
|
|
|
|
|
|
* Sets *pre_adj = depth change BEFORE indenting (leading '}') |
|
33
|
|
|
|
|
|
|
* Sets *post_adj = net depth change from rest of line |
|
34
|
|
|
|
|
|
|
* ══════════════════════════════════════════════════════════════════ */ |
|
35
|
|
|
|
|
|
|
|
|
36
|
166
|
|
|
|
|
|
static void eshu_css_scan_line(eshu_css_ctx_t *ctx, |
|
37
|
|
|
|
|
|
|
const char *content, const char *eol, |
|
38
|
|
|
|
|
|
|
int *pre_adj, int *post_adj) { |
|
39
|
166
|
|
|
|
|
|
const char *p = content; |
|
40
|
166
|
|
|
|
|
|
int first_close_counted = 0; |
|
41
|
166
|
|
|
|
|
|
*pre_adj = 0; |
|
42
|
166
|
|
|
|
|
|
*post_adj = 0; |
|
43
|
|
|
|
|
|
|
|
|
44
|
1645
|
100
|
|
|
|
|
while (p < eol) { |
|
45
|
|
|
|
|
|
|
/* ── Block comment state ── */ |
|
46
|
1479
|
100
|
|
|
|
|
if (ctx->state == ESHU_CSS_COMMENT) { |
|
47
|
106
|
100
|
|
|
|
|
if (p + 1 < eol && p[0] == '*' && p[1] == '/') { |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
48
|
7
|
|
|
|
|
|
ctx->state = ESHU_CODE; |
|
49
|
7
|
|
|
|
|
|
p += 2; |
|
50
|
7
|
|
|
|
|
|
continue; |
|
51
|
|
|
|
|
|
|
} |
|
52
|
99
|
|
|
|
|
|
p++; |
|
53
|
99
|
|
|
|
|
|
continue; |
|
54
|
|
|
|
|
|
|
} |
|
55
|
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
/* ── Double-quoted string ── */ |
|
57
|
1373
|
100
|
|
|
|
|
if (ctx->state == ESHU_CSS_STRING_DQ) { |
|
58
|
19
|
100
|
|
|
|
|
if (*p == '\\' && p + 1 < eol) { |
|
|
|
50
|
|
|
|
|
|
|
59
|
2
|
|
|
|
|
|
p += 2; /* skip escaped char */ |
|
60
|
2
|
|
|
|
|
|
continue; |
|
61
|
|
|
|
|
|
|
} |
|
62
|
17
|
100
|
|
|
|
|
if (*p == '"') { |
|
63
|
2
|
|
|
|
|
|
ctx->state = ESHU_CODE; |
|
64
|
|
|
|
|
|
|
} |
|
65
|
17
|
|
|
|
|
|
p++; |
|
66
|
17
|
|
|
|
|
|
continue; |
|
67
|
|
|
|
|
|
|
} |
|
68
|
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
/* ── Single-quoted string ── */ |
|
70
|
1354
|
100
|
|
|
|
|
if (ctx->state == ESHU_CSS_STRING_SQ) { |
|
71
|
10
|
50
|
|
|
|
|
if (*p == '\\' && p + 1 < eol) { |
|
|
|
0
|
|
|
|
|
|
|
72
|
0
|
|
|
|
|
|
p += 2; |
|
73
|
0
|
|
|
|
|
|
continue; |
|
74
|
|
|
|
|
|
|
} |
|
75
|
10
|
100
|
|
|
|
|
if (*p == '\'') { |
|
76
|
1
|
|
|
|
|
|
ctx->state = ESHU_CODE; |
|
77
|
|
|
|
|
|
|
} |
|
78
|
10
|
|
|
|
|
|
p++; |
|
79
|
10
|
|
|
|
|
|
continue; |
|
80
|
|
|
|
|
|
|
} |
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
/* ── url() content ── */ |
|
83
|
1344
|
100
|
|
|
|
|
if (ctx->state == ESHU_CSS_URL) { |
|
84
|
60
|
100
|
|
|
|
|
if (*p == ')') { |
|
85
|
4
|
|
|
|
|
|
ctx->state = ESHU_CODE; |
|
86
|
|
|
|
|
|
|
} |
|
87
|
60
|
|
|
|
|
|
p++; |
|
88
|
60
|
|
|
|
|
|
continue; |
|
89
|
|
|
|
|
|
|
} |
|
90
|
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
/* ── Normal CODE state ── */ |
|
92
|
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
/* Block comment: /* */ |
|
94
|
1284
|
100
|
|
|
|
|
if (p + 1 < eol && p[0] == '/' && p[1] == '*') { |
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
95
|
7
|
|
|
|
|
|
ctx->state = ESHU_CSS_COMMENT; |
|
96
|
7
|
|
|
|
|
|
p += 2; |
|
97
|
7
|
|
|
|
|
|
continue; |
|
98
|
|
|
|
|
|
|
} |
|
99
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
/* Double-quoted string */ |
|
101
|
1277
|
100
|
|
|
|
|
if (*p == '"') { |
|
102
|
3
|
|
|
|
|
|
ctx->state = ESHU_CSS_STRING_DQ; |
|
103
|
3
|
|
|
|
|
|
p++; |
|
104
|
3
|
|
|
|
|
|
continue; |
|
105
|
|
|
|
|
|
|
} |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
/* Single-quoted string */ |
|
108
|
1274
|
100
|
|
|
|
|
if (*p == '\'') { |
|
109
|
1
|
|
|
|
|
|
ctx->state = ESHU_CSS_STRING_SQ; |
|
110
|
1
|
|
|
|
|
|
p++; |
|
111
|
1
|
|
|
|
|
|
continue; |
|
112
|
|
|
|
|
|
|
} |
|
113
|
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
/* url( — skip content until ) */ |
|
115
|
1273
|
100
|
|
|
|
|
if (p + 3 < eol && p[0] == 'u' && p[1] == 'r' && p[2] == 'l' && p[3] == '(') { |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
116
|
4
|
|
|
|
|
|
ctx->state = ESHU_CSS_URL; |
|
117
|
4
|
|
|
|
|
|
p += 4; |
|
118
|
4
|
|
|
|
|
|
continue; |
|
119
|
|
|
|
|
|
|
} |
|
120
|
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
/* Opening brace */ |
|
122
|
1269
|
100
|
|
|
|
|
if (*p == '{') { |
|
123
|
51
|
|
|
|
|
|
*post_adj += 1; |
|
124
|
51
|
|
|
|
|
|
p++; |
|
125
|
51
|
|
|
|
|
|
continue; |
|
126
|
|
|
|
|
|
|
} |
|
127
|
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
/* Closing brace */ |
|
129
|
1218
|
100
|
|
|
|
|
if (*p == '}') { |
|
130
|
50
|
100
|
|
|
|
|
if (p == content && !first_close_counted) { |
|
|
|
50
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
/* Leading '}' — apply as pre-adjust */ |
|
132
|
48
|
|
|
|
|
|
*pre_adj -= 1; |
|
133
|
48
|
|
|
|
|
|
first_close_counted = 1; |
|
134
|
|
|
|
|
|
|
} else { |
|
135
|
2
|
|
|
|
|
|
*post_adj -= 1; |
|
136
|
|
|
|
|
|
|
} |
|
137
|
50
|
|
|
|
|
|
p++; |
|
138
|
50
|
|
|
|
|
|
continue; |
|
139
|
|
|
|
|
|
|
} |
|
140
|
|
|
|
|
|
|
|
|
141
|
1168
|
|
|
|
|
|
p++; |
|
142
|
|
|
|
|
|
|
} |
|
143
|
166
|
|
|
|
|
|
} |
|
144
|
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
/* ══════════════════════════════════════════════════════════════════ |
|
146
|
|
|
|
|
|
|
* Process a single line |
|
147
|
|
|
|
|
|
|
* ══════════════════════════════════════════════════════════════════ */ |
|
148
|
|
|
|
|
|
|
|
|
149
|
167
|
|
|
|
|
|
static void eshu_css_process_line(eshu_css_ctx_t *ctx, |
|
150
|
|
|
|
|
|
|
const char *line, const char *eol, |
|
151
|
|
|
|
|
|
|
eshu_buf_t *out) { |
|
152
|
167
|
|
|
|
|
|
const char *content = eshu_skip_leading_ws(line); |
|
153
|
167
|
|
|
|
|
|
int content_len = (int)(eol - content); |
|
154
|
167
|
100
|
|
|
|
|
int is_blank = (content == eol || *content == '\n'); |
|
|
|
50
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
/* Blank line */ |
|
157
|
167
|
100
|
|
|
|
|
if (is_blank) { |
|
158
|
1
|
50
|
|
|
|
|
if (*eol == '\n') eshu_buf_putc(out, '\n'); |
|
159
|
1
|
|
|
|
|
|
return; |
|
160
|
|
|
|
|
|
|
} |
|
161
|
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
/* Determine depth adjustments */ |
|
163
|
|
|
|
|
|
|
{ |
|
164
|
166
|
|
|
|
|
|
int pre_adj = 0, post_adj = 0; |
|
165
|
|
|
|
|
|
|
int indent_depth; |
|
166
|
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
/* Check for leading '}' */ |
|
168
|
166
|
100
|
|
|
|
|
if (*content == '}') { |
|
169
|
49
|
|
|
|
|
|
pre_adj = -1; |
|
170
|
|
|
|
|
|
|
} |
|
171
|
|
|
|
|
|
|
|
|
172
|
166
|
|
|
|
|
|
indent_depth = ctx->depth + pre_adj; |
|
173
|
166
|
50
|
|
|
|
|
if (indent_depth < 0) indent_depth = 0; |
|
174
|
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
/* Emit indentation + content */ |
|
176
|
166
|
|
|
|
|
|
eshu_emit_indent(out, indent_depth, &ctx->cfg); |
|
177
|
166
|
|
|
|
|
|
eshu_buf_write_trimmed(out, content, content_len); |
|
178
|
166
|
50
|
|
|
|
|
if (*eol == '\n') eshu_buf_putc(out, '\n'); |
|
179
|
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
/* Scan line for full depth changes */ |
|
181
|
|
|
|
|
|
|
{ |
|
182
|
166
|
|
|
|
|
|
int scan_pre = 0, scan_post = 0; |
|
183
|
166
|
|
|
|
|
|
eshu_css_scan_line(ctx, content, eol, &scan_pre, &scan_post); |
|
184
|
|
|
|
|
|
|
|
|
185
|
166
|
|
|
|
|
|
ctx->depth += scan_pre + scan_post; |
|
186
|
166
|
50
|
|
|
|
|
if (ctx->depth < 0) ctx->depth = 0; |
|
187
|
|
|
|
|
|
|
} |
|
188
|
|
|
|
|
|
|
} |
|
189
|
|
|
|
|
|
|
} |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
/* ══════════════════════════════════════════════════════════════════ |
|
192
|
|
|
|
|
|
|
* Public API |
|
193
|
|
|
|
|
|
|
* ══════════════════════════════════════════════════════════════════ */ |
|
194
|
|
|
|
|
|
|
|
|
195
|
31
|
|
|
|
|
|
static char * eshu_indent_css(const char *src, size_t src_len, |
|
196
|
|
|
|
|
|
|
const eshu_config_t *cfg, size_t *out_len) { |
|
197
|
|
|
|
|
|
|
eshu_css_ctx_t ctx; |
|
198
|
|
|
|
|
|
|
eshu_buf_t out; |
|
199
|
31
|
|
|
|
|
|
const char *p = src; |
|
200
|
31
|
|
|
|
|
|
const char *end = src + src_len; |
|
201
|
|
|
|
|
|
|
char *result; |
|
202
|
|
|
|
|
|
|
|
|
203
|
31
|
|
|
|
|
|
eshu_css_ctx_init(&ctx, cfg); |
|
204
|
31
|
|
|
|
|
|
eshu_buf_init(&out, src_len + 256); |
|
205
|
|
|
|
|
|
|
|
|
206
|
198
|
100
|
|
|
|
|
while (p < end) { |
|
207
|
167
|
|
|
|
|
|
const char *eol = eshu_find_eol(p); |
|
208
|
167
|
|
|
|
|
|
eshu_css_process_line(&ctx, p, eol, &out); |
|
209
|
167
|
50
|
|
|
|
|
p = (*eol == '\n') ? eol + 1 : eol; |
|
210
|
167
|
50
|
|
|
|
|
if (p > end) p = end; |
|
211
|
|
|
|
|
|
|
} |
|
212
|
|
|
|
|
|
|
|
|
213
|
31
|
|
|
|
|
|
*out_len = out.len; |
|
214
|
31
|
|
|
|
|
|
result = out.data; |
|
215
|
31
|
|
|
|
|
|
return result; |
|
216
|
|
|
|
|
|
|
} |
|
217
|
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
#endif /* ESHU_CSS_H */ |