10#include <ascii-chat/options/manpage/parser.h>
11#include <ascii-chat/log/logging.h>
12#include <ascii-chat/common.h>
13#include <ascii-chat/platform/util.h>
31static bool is_marker_line(
const char *line,
const char **type_out,
char **section_out) {
38 while (isspace((
unsigned char)*p)) {
43 if (strncmp(p,
".\\\"", 3) != 0 && strncmp(p,
".\"", 2) != 0) {
48 if (p[0] ==
'.' && p[1] ==
'\\' && p[2] ==
'"') {
50 }
else if (p[0] ==
'.' && p[1] ==
'"') {
55 while (isspace((
unsigned char)*p)) {
60 const char *type = NULL;
61 const char *section_start = NULL;
63 if (strncmp(p,
"AUTO-START:", 11) == 0) {
65 section_start = p + 11;
66 }
else if (strncmp(p,
"AUTO-END:", 9) == 0) {
68 section_start = p + 9;
69 }
else if (strncmp(p,
"MANUAL-START:", 13) == 0) {
71 section_start = p + 13;
72 }
else if (strncmp(p,
"MANUAL-END:", 11) == 0) {
74 section_start = p + 11;
75 }
else if (strncmp(p,
"MERGE-START:", 12) == 0) {
77 section_start = p + 12;
78 }
else if (strncmp(p,
"MERGE-END:", 10) == 0) {
80 section_start = p + 10;
86 while (isspace((
unsigned char)*section_start)) {
91 const char *section_end = section_start;
92 while (*section_end && *section_end !=
'\n' && *section_end !=
'\r') {
96 if (section_end > section_start) {
101 size_t len = section_end - section_start;
102 *section_out = SAFE_MALLOC(len + 1,
char *);
103 memcpy(*section_out, section_start, len);
104 (*section_out)[len] =
'\0';
106 char *s = *section_out + len - 1;
107 while (s >= *section_out && isspace((
unsigned char)*s)) {
120static bool is_section_header(
const char *line,
char **section_name_out) {
126 const char *p = line;
127 while (isspace((
unsigned char)*p)) {
132 if (strncmp(p,
".SH", 3) != 0) {
139 while (isspace((
unsigned char)*p)) {
144 const char *name_start = p;
145 const char *name_end = name_start;
146 while (*name_end && *name_end !=
'\n' && *name_end !=
'\r') {
150 if (name_end > name_start) {
151 size_t len = name_end - name_start;
152 *section_name_out = SAFE_MALLOC(len + 1,
char *);
153 memcpy(*section_name_out, name_start, len);
154 (*section_name_out)[len] =
'\0';
156 char *s = *section_name_out + len - 1;
157 while (s >= *section_name_out && isspace((
unsigned char)*s)) {
161 len = strlen(*section_name_out);
162 if (len >= 2 && (*section_name_out)[0] ==
'"' && (*section_name_out)[len - 1] ==
'"') {
163 memmove(*section_name_out, *section_name_out + 1, len - 2);
164 (*section_name_out)[len - 2] =
'\0';
175static section_type_t type_string_to_enum(
const char *type_str) {
177 return SECTION_TYPE_UNMARKED;
179 if (strcmp(type_str,
"AUTO") == 0) {
180 return SECTION_TYPE_AUTO;
182 if (strcmp(type_str,
"MANUAL") == 0) {
183 return SECTION_TYPE_MANUAL;
185 if (strcmp(type_str,
"MERGE") == 0) {
186 return SECTION_TYPE_MERGE;
188 return SECTION_TYPE_UNMARKED;
194static asciichat_error_t parse_sections_internal(FILE *f, parsed_section_t **out_sections,
size_t *out_count) {
195 if (!f || !out_sections || !out_count) {
196 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters for parse_sections_internal");
199 parsed_section_t *sections = NULL;
200 size_t capacity = 16;
202 sections = SAFE_MALLOC(capacity *
sizeof(parsed_section_t), parsed_section_t *);
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 *);
215 const char *current_type = NULL;
216 char *current_marker_section = NULL;
217 bool in_marked_section =
false;
219 while (platform_getline(&line, &line_len, f) != -1) {
222 const char *marker_type = NULL;
223 char *marker_section = NULL;
224 bool is_marker = is_marker_line(line, &marker_type, &marker_section);
226 char *section_header_name = NULL;
227 bool is_header = is_section_header(line, §ion_header_name);
231 if (strstr(line,
"-START:") != NULL) {
233 current_type = marker_type;
234 if (current_marker_section) {
235 SAFE_FREE(current_marker_section);
237 current_marker_section = marker_section;
238 in_marked_section =
true;
239 marker_section = NULL;
240 }
else if (strstr(line,
"-END:") != NULL) {
242 if (marker_section) {
243 SAFE_FREE(marker_section);
245 in_marked_section =
false;
247 if (current_marker_section) {
248 SAFE_FREE(current_marker_section);
249 current_marker_section = NULL;
252 if (marker_section) {
253 SAFE_FREE(marker_section);
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 *);
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 *);
278 if (count >= capacity) {
280 sections = SAFE_REALLOC(sections, capacity *
sizeof(parsed_section_t), parsed_section_t *);
283 current_section = §ions[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;
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 *);
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) {
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 *);
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;
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);
322 if (current_marker_section) {
323 SAFE_FREE(current_marker_section);
329 *out_sections = sections;
331 log_debug(
"Parsed %zu sections from file", count);
340 if (!f || !out_sections || !out_count) {
341 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters for manpage_parser_parse_file");
344 return parse_sections_internal(f, out_sections, 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");
356 return SET_ERRNO_SYS(ERROR_CONFIG,
"Failed to create temporary file for memory parsing");
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) {
367 return SET_ERRNO_SYS(ERROR_CONFIG,
"Failed to write complete content to temporary file");
374 asciichat_error_t err = parse_sections_internal(tmp, out_sections, out_count);
385 for (
size_t i = 0; i < count; i++) {
386 if (sections[i].section_name) {
387 SAFE_FREE(sections[i].section_name);
389 if (sections[i].content) {
390 SAFE_FREE(sections[i].content);
398 const char *section_name) {
399 if (!sections || !section_name) {
403 for (
size_t i = 0; i < count; i++) {
404 if (sections[i].section_name && strcmp(sections[i].section_name, section_name) == 0) {
asciichat_error_t manpage_parser_parse_file(FILE *f, parsed_section_t **out_sections, size_t *out_count)
const parsed_section_t * manpage_parser_find_section(const parsed_section_t *sections, size_t count, const char *section_name)
asciichat_error_t manpage_parser_parse_memory(const char *content, size_t content_len, parsed_section_t **out_sections, size_t *out_count)
void manpage_parser_free_sections(parsed_section_t *sections, size_t count)
FILE * platform_tmpfile(void)