ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
parser.c
Go to the documentation of this file.
1
11#include <ascii-chat/log/log.h>
12#include <ascii-chat/common.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16#include <ctype.h>
17
18// ============================================================================
19// Helper Functions
20// ============================================================================
21
30static bool is_marker_line(const char *line, const char **type_out, char **section_out) {
31 if (!line) {
32 return false;
33 }
34
35 // Skip leading whitespace
36 const char *p = line;
37 while (isspace((unsigned char)*p)) {
38 p++;
39 }
40
41 // Check for groff comment: .\ " or ."
42 if (strncmp(p, ".\\\"", 3) != 0 && strncmp(p, ".\"", 2) != 0) {
43 return false;
44 }
45
46 // Skip comment marker
47 if (p[0] == '.' && p[1] == '\\' && p[2] == '"') {
48 p += 3;
49 } else if (p[0] == '.' && p[1] == '"') {
50 p += 2;
51 }
52
53 // Skip whitespace after comment marker
54 while (isspace((unsigned char)*p)) {
55 p++;
56 }
57
58 // Check for marker pattern: TYPE-START: SECTION or TYPE-END: SECTION
59 const char *type = NULL;
60 const char *section_start = NULL;
61
62 if (strncmp(p, "AUTO-START:", 11) == 0) {
63 type = "AUTO";
64 section_start = p + 11;
65 } else if (strncmp(p, "AUTO-END:", 9) == 0) {
66 type = "AUTO";
67 section_start = p + 9;
68 } else if (strncmp(p, "MANUAL-START:", 13) == 0) {
69 type = "MANUAL";
70 section_start = p + 13;
71 } else if (strncmp(p, "MANUAL-END:", 11) == 0) {
72 type = "MANUAL";
73 section_start = p + 11;
74 } else if (strncmp(p, "MERGE-START:", 12) == 0) {
75 type = "MERGE";
76 section_start = p + 12;
77 } else if (strncmp(p, "MERGE-END:", 10) == 0) {
78 type = "MERGE";
79 section_start = p + 10;
80 } else {
81 return false;
82 }
83
84 // Skip whitespace before section name
85 while (isspace((unsigned char)*section_start)) {
86 section_start++;
87 }
88
89 // Extract section name (until end of line)
90 const char *section_end = section_start;
91 while (*section_end && *section_end != '\n' && *section_end != '\r') {
92 section_end++;
93 }
94
95 if (section_end > section_start) {
96 if (type_out) {
97 *type_out = type;
98 }
99 if (section_out) {
100 size_t len = section_end - section_start;
101 *section_out = SAFE_MALLOC(len + 1, char *);
102 memcpy(*section_out, section_start, len);
103 (*section_out)[len] = '\0';
104 // Trim trailing whitespace
105 char *s = *section_out + len - 1;
106 while (s >= *section_out && isspace((unsigned char)*s)) {
107 *s-- = '\0';
108 }
109 }
110 return true;
111 }
112
113 return false;
114}
115
119static bool is_section_header(const char *line, char **section_name_out) {
120 if (!line) {
121 return false;
122 }
123
124 // Skip leading whitespace
125 const char *p = line;
126 while (isspace((unsigned char)*p)) {
127 p++;
128 }
129
130 // Check for .SH directive
131 if (strncmp(p, ".SH", 3) != 0) {
132 return false;
133 }
134
135 p += 3;
136
137 // Skip whitespace
138 while (isspace((unsigned char)*p)) {
139 p++;
140 }
141
142 // Extract section name (until end of line)
143 const char *name_start = p;
144 const char *name_end = name_start;
145 while (*name_end && *name_end != '\n' && *name_end != '\r') {
146 name_end++;
147 }
148
149 if (name_end > name_start) {
150 size_t len = name_end - name_start;
151 *section_name_out = SAFE_MALLOC(len + 1, char *);
152 memcpy(*section_name_out, name_start, len);
153 (*section_name_out)[len] = '\0';
154 // Trim trailing whitespace
155 char *s = *section_name_out + len - 1;
156 while (s >= *section_name_out && isspace((unsigned char)*s)) {
157 *s-- = '\0';
158 }
159 // Remove quotes if present
160 len = strlen(*section_name_out);
161 if (len >= 2 && (*section_name_out)[0] == '"' && (*section_name_out)[len - 1] == '"') {
162 memmove(*section_name_out, *section_name_out + 1, len - 2);
163 (*section_name_out)[len - 2] = '\0';
164 }
165 return true;
166 }
167
168 return false;
169}
170
174static section_type_t type_string_to_enum(const char *type_str) {
175 if (!type_str) {
177 }
178 if (strcmp(type_str, "AUTO") == 0) {
179 return SECTION_TYPE_AUTO;
180 }
181 if (strcmp(type_str, "MANUAL") == 0) {
182 return SECTION_TYPE_MANUAL;
183 }
184 if (strcmp(type_str, "MERGE") == 0) {
185 return SECTION_TYPE_MERGE;
186 }
188}
189
193static asciichat_error_t parse_sections_internal(FILE *f, parsed_section_t **out_sections, size_t *out_count) {
194 if (!f || !out_sections || !out_count) {
195 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for parse_sections_internal");
196 }
197
198 parsed_section_t *sections = NULL;
199 size_t capacity = 16;
200 size_t count = 0;
201 // NOLINTNEXTLINE(clang-analyzer-unix.Errno) - SAFE_MALLOC handles error
202 sections = SAFE_MALLOC(capacity * sizeof(parsed_section_t), parsed_section_t *);
203
204 char *line = NULL;
205 size_t line_len = 0;
206 size_t line_num = 0;
207
208 parsed_section_t *current_section = NULL;
209 char *current_content = NULL;
210 size_t current_content_capacity = 4096;
211 size_t current_content_len = 0;
212 current_content = SAFE_MALLOC(current_content_capacity, char *);
213
214 // Track markers for current section
215 const char *current_type = NULL;
216 char *current_marker_section = NULL;
217 bool in_marked_section = false;
218
219 while (platform_getline(&line, &line_len, f) != -1) {
220 line_num++;
221
222 const char *marker_type = NULL;
223 char *marker_section = NULL;
224 bool is_marker = is_marker_line(line, &marker_type, &marker_section);
225
226 char *section_header_name = NULL;
227 bool is_header = is_section_header(line, &section_header_name);
228
229 if (is_marker) {
230 // Check if this is a START marker
231 if (strstr(line, "-START:") != NULL) {
232 // Start of a marked section
233 current_type = marker_type;
234 if (current_marker_section) {
235 SAFE_FREE(current_marker_section);
236 }
237 current_marker_section = marker_section;
238 in_marked_section = true;
239 marker_section = NULL; // Ownership transferred
240 } else if (strstr(line, "-END:") != NULL) {
241 // End of marked section
242 if (marker_section) {
243 SAFE_FREE(marker_section);
244 }
245 in_marked_section = false;
246 current_type = NULL;
247 if (current_marker_section) {
248 SAFE_FREE(current_marker_section);
249 current_marker_section = NULL;
250 }
251 } else {
252 if (marker_section) {
253 SAFE_FREE(marker_section);
254 }
255 }
256 }
257
258 if (is_header) {
259 // Finalize previous section if exists
260 if (current_section) {
261 if (current_content && current_content_len > 0) {
262 current_section->content = current_content;
263 current_section->content_len = current_content_len;
264 current_content = NULL;
265 current_content_capacity = 4096;
266 current_content_len = 0;
267 current_content = SAFE_MALLOC(current_content_capacity, char *);
268 } else {
269 SAFE_FREE(current_content);
270 current_content = NULL;
271 current_content_capacity = 4096;
272 current_content_len = 0;
273 current_content = SAFE_MALLOC(current_content_capacity, char *);
274 }
275 }
276
277 // Start new section
278 if (count >= capacity) {
279 capacity *= 2;
280 sections = SAFE_REALLOC(sections, capacity * sizeof(parsed_section_t), parsed_section_t *);
281 }
282
283 current_section = &sections[count++];
284 memset(current_section, 0, sizeof(parsed_section_t));
285 current_section->section_name = section_header_name;
286 current_section->start_line = line_num;
287 current_section->end_line = line_num;
288 current_section->type = in_marked_section ? type_string_to_enum(current_type) : SECTION_TYPE_UNMARKED;
289 current_section->has_markers = in_marked_section;
290
291 // Append header line to content
292 size_t line_strlen = strlen(line);
293 if (current_content_len + line_strlen + 1 >= current_content_capacity) {
294 current_content_capacity = (current_content_len + line_strlen + 1) * 2;
295 current_content = SAFE_REALLOC(current_content, current_content_capacity, char *);
296 }
297 memcpy(current_content + current_content_len, line, line_strlen);
298 current_content_len += line_strlen;
299 current_content[current_content_len] = '\0';
300 } else if (current_section) {
301 // Append line to current section content
302 size_t line_strlen = strlen(line);
303 if (current_content_len + line_strlen + 1 >= current_content_capacity) {
304 current_content_capacity = (current_content_len + line_strlen + 1) * 2;
305 current_content = SAFE_REALLOC(current_content, current_content_capacity, char *);
306 }
307 memcpy(current_content + current_content_len, line, line_strlen);
308 current_content_len += line_strlen;
309 current_content[current_content_len] = '\0';
310 current_section->end_line = line_num;
311 }
312 }
313
314 // Finalize last section
315 if (current_section && current_content && current_content_len > 0) {
316 current_section->content = current_content;
317 current_section->content_len = current_content_len;
318 } else if (current_content) {
319 SAFE_FREE(current_content);
320 }
321
322 if (current_marker_section) {
323 SAFE_FREE(current_marker_section);
324 }
325 if (line) {
326 free(line);
327 }
328
329 *out_sections = sections;
330 *out_count = count;
331 log_debug("Parsed %zu sections from file", count);
332 return ASCIICHAT_OK;
333}
334
335// ============================================================================
336// Public API
337// ============================================================================
338
339asciichat_error_t manpage_parser_parse_file(FILE *f, parsed_section_t **out_sections, size_t *out_count) {
340 if (!f || !out_sections || !out_count) {
341 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for manpage_parser_parse_file");
342 }
343
344 return parse_sections_internal(f, out_sections, out_count);
345}
346
347asciichat_error_t manpage_parser_parse_memory(const char *content, size_t content_len, parsed_section_t **out_sections,
348 size_t *out_count) {
349 if (!content || content_len == 0 || !out_sections || !out_count) {
350 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for manpage_parser_parse_memory");
351 }
352
353 // Create temporary file from memory
354 FILE *tmp = platform_tmpfile();
355 if (!tmp) {
356 return SET_ERRNO_SYS(ERROR_CONFIG, "Failed to create temporary file for memory parsing");
357 }
358
359 // Write content to temporary file
360 // Write content_len + 1 to include the null terminator.
361 // The embedded string is content_len + 1 bytes (content_len chars + null terminator).
362 // platform_getline() needs the null terminator to properly handle EOF.
363 size_t bytes_to_write = content_len + 1;
364 size_t written = fwrite(content, 1, bytes_to_write, tmp);
365 if (written != bytes_to_write) {
366 fclose(tmp);
367 return SET_ERRNO_SYS(ERROR_CONFIG, "Failed to write complete content to temporary file");
368 }
369
370 // Rewind to beginning for reading
371 if (fseek(tmp, 0, SEEK_SET) != 0) {
372 fclose(tmp);
373 return SET_ERRNO_SYS(ERROR_CONFIG, "Failed to rewind temporary file");
374 }
375
376 // Parse using common function
377 asciichat_error_t err = parse_sections_internal(tmp, out_sections, out_count);
378 fclose(tmp);
379
380 return err;
381}
382
383void manpage_parser_free_sections(parsed_section_t *sections, size_t count) {
384 if (!sections) {
385 return;
386 }
387
388 for (size_t i = 0; i < count; i++) {
389 if (sections[i].section_name) {
390 SAFE_FREE(sections[i].section_name);
391 }
392 if (sections[i].content) {
393 SAFE_FREE(sections[i].content);
394 }
395 }
396
397 SAFE_FREE(sections);
398}
399
401 const char *section_name) {
402 if (!sections || !section_name) {
403 return NULL;
404 }
405
406 for (size_t i = 0; i < count; i++) {
407 if (sections[i].section_name && strcmp(sections[i].section_name, section_name) == 0) {
408 return &sections[i];
409 }
410 }
411
412 return NULL;
413}
⚙️ Common definitions, error codes, macros, and types shared throughout the application
#define SAFE_REALLOC(ptr, size, cast)
Definition common.h:284
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define SET_ERRNO_SYS(code, context_msg,...)
Set error code with custom message and system error context, returning the error code.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_CONFIG
Definition error_codes.h:57
@ ERROR_INVALID_PARAM
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
FILE * platform_tmpfile(void)
Create a temporary file (tmpfile replacement)
ssize_t platform_getline(char **lineptr, size_t *n, FILE *stream)
Cross-platform getline implementation.
📝 Logging API with multiple log levels and terminal output control
section_type_t
Section type marker.
Definition manpage.h:28
@ SECTION_TYPE_MERGE
Intelligently merged: auto + manual content.
Definition manpage.h:31
@ SECTION_TYPE_UNMARKED
No marker found, defaults to MANUAL.
Definition manpage.h:32
@ SECTION_TYPE_AUTO
Fully auto-generated, manual edits lost on regeneration.
Definition manpage.h:29
@ SECTION_TYPE_MANUAL
Manually maintained, never touched by generator.
Definition manpage.h:30
asciichat_error_t manpage_parser_parse_file(FILE *f, parsed_section_t **out_sections, size_t *out_count)
Parse man page sections from a FILE handle.
Definition parser.c:339
const parsed_section_t * manpage_parser_find_section(const parsed_section_t *sections, size_t count, const char *section_name)
Find section by name.
Definition parser.c:400
asciichat_error_t manpage_parser_parse_memory(const char *content, size_t content_len, parsed_section_t **out_sections, size_t *out_count)
Parse man page sections from memory buffer.
Definition parser.c:347
void manpage_parser_free_sections(parsed_section_t *sections, size_t count)
Free parsed sections array.
Definition parser.c:383
Man page template parsing and section extraction.
Parsed section information.
Definition manpage.h:38
bool has_markers
True if marked with AUTO/MANUAL/MERGE markers.
Definition manpage.h:45
char * content
Full section content (including .SH header)
Definition manpage.h:41
size_t content_len
Length of content.
Definition manpage.h:42
size_t end_line
Line number where section ends (1-based)
Definition manpage.h:44
size_t start_line
Line number where section starts (1-based)
Definition manpage.h:43
section_type_t type
Section type (AUTO/MANUAL/MERGE/UNMARKED)
Definition manpage.h:40
char * section_name
Section name (e.g., "ENVIRONMENT", "OPTIONS")
Definition manpage.h:39