ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
options/manpage.c
Go to the documentation of this file.
1
25#include <ascii-chat/common.h>
26#include <ascii-chat/log/log.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <ctype.h>
33
34// =============================================================================
35// Helper Functions (exported for content generators)
36// =============================================================================
37
38const char *escape_groff_special(const char *str) {
39 return str ? str : "";
40}
41
42const char *format_mode_names(option_mode_bitmask_t mode_bitmask) {
43 if (mode_bitmask == 0) {
44 return NULL;
45 }
46
47 if ((mode_bitmask & OPTION_MODE_BINARY) && !(mode_bitmask & 0x1F)) {
48 return "global";
49 }
50
51 option_mode_bitmask_t user_modes_mask = (1 << MODE_SERVER) | (1 << MODE_CLIENT) | (1 << MODE_MIRROR) |
53 if ((mode_bitmask & user_modes_mask) == user_modes_mask) {
54 // If all user modes are set, check if binary-level is also set
55 if (mode_bitmask & OPTION_MODE_BINARY) {
56 return "global, client, server, mirror, discovery-service";
57 }
58 return "all modes";
59 }
60
61 static char mode_str[256];
62 mode_str[0] = '\0';
63 int pos = 0;
64
65 // Add global if binary-level is set
66 if (mode_bitmask & OPTION_MODE_BINARY) {
67 pos += safe_snprintf(mode_str + pos, sizeof(mode_str) - pos, "global");
68 }
69
70 // List modes in order: default, client, server, mirror, discovery-service
71 if (mode_bitmask & (1 << MODE_DISCOVERY)) {
72 pos += safe_snprintf(mode_str + pos, sizeof(mode_str) - pos, "%sdefault", pos > 0 ? ", " : "");
73 }
74 if (mode_bitmask & (1 << MODE_CLIENT)) {
75 pos += safe_snprintf(mode_str + pos, sizeof(mode_str) - pos, "%sclient", pos > 0 ? ", " : "");
76 }
77 if (mode_bitmask & (1 << MODE_SERVER)) {
78 pos += safe_snprintf(mode_str + pos, sizeof(mode_str) - pos, "%sserver", pos > 0 ? ", " : "");
79 }
80 if (mode_bitmask & (1 << MODE_MIRROR)) {
81 pos += safe_snprintf(mode_str + pos, sizeof(mode_str) - pos, "%smirror", pos > 0 ? ", " : "");
82 }
83 if (mode_bitmask & (1 << MODE_DISCOVERY_SERVICE)) {
84 pos += safe_snprintf(mode_str + pos, sizeof(mode_str) - pos, "%sdiscovery-service", pos > 0 ? ", " : "");
85 }
86
87 return pos > 0 ? mode_str : NULL;
88}
89
90// =============================================================================
91// Main Public API Functions
92// =============================================================================
93
95 const char *mode_name, const char *output_path,
96 const char *brief_description) {
97 if (!config || !program_name || !brief_description) {
98 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing required parameters for man page generation");
99 }
100
101 FILE *f = NULL;
102 bool should_close = false;
103
104 if (output_path) {
105 f = platform_fopen("file_stream", output_path, "w");
106 if (!f) {
107 return SET_ERRNO_SYS(ERROR_CONFIG, "Failed to open output file: %s", output_path);
108 }
109 should_close = true;
110 } else {
111 f = stdout;
112 }
113
114 // Write title/header
115 manpage_fmt_write_title(f, program_name, mode_name, brief_description);
116
117 // Write SYNOPSIS
118 char *synopsis_content = NULL;
119 size_t synopsis_len = 0;
120 asciichat_error_t err = manpage_merger_generate_synopsis(mode_name, &synopsis_content, &synopsis_len);
121 if (err == ASCIICHAT_OK && synopsis_content && synopsis_len > 0) {
122 fprintf(f, "%s", synopsis_content);
123 manpage_merger_free_content(synopsis_content);
124 }
125
126 // Write POSITIONAL ARGUMENTS if present
127 if (config->num_positional_args > 0) {
128 char *pos_content = manpage_content_generate_positional(config);
129 if (pos_content && *pos_content != '\0') {
130 fprintf(f, "%s", pos_content);
131 }
133 }
134
135 // Write DESCRIPTION
136 manpage_fmt_write_section(f, "DESCRIPTION");
137 fprintf(f, ".B ascii-chat\nis a terminal-based video chat application that converts webcam video to ASCII\n");
138 fprintf(f, "art in real-time. It enables video chat directly in your terminal, whether you're\n");
139 fprintf(f, "using a local console, a remote SSH session, or any terminal emulator.\n");
141
142 // Write USAGE
143 char *usage_content = NULL;
144 size_t usage_len = 0;
145 err = manpage_merger_generate_usage(config, &usage_content, &usage_len);
146 if (err == ASCIICHAT_OK && usage_content && usage_len > 0) {
147 fprintf(f, ".SH USAGE\n%s", usage_content);
148 manpage_merger_free_content(usage_content);
149 }
150
151 // Write OPTIONS
152 if (config->num_descriptors > 0) {
153 char *options_content = manpage_content_generate_options(config);
154 if (options_content && *options_content != '\0') {
155 fprintf(f, "%s", options_content);
156 }
157 manpage_content_free_options(options_content);
158 }
159
160 // Write EXAMPLES if present
161 if (config->num_examples > 0) {
162 char *examples_content = manpage_content_generate_examples(config);
163 if (examples_content && *examples_content != '\0') {
164 fprintf(f, "%s", examples_content);
165 }
166 manpage_content_free_examples(examples_content);
167 }
168
169 // Write ENVIRONMENT if present
170 bool has_env_vars = false;
171 for (size_t i = 0; i < config->num_descriptors; i++) {
172 if (config->descriptors[i].env_var_name) {
173 has_env_vars = true;
174 break;
175 }
176 }
177
178 if (has_env_vars) {
179 char *env_content = manpage_content_generate_environment(config);
180 if (env_content && *env_content != '\0') {
181 fprintf(f, "%s", env_content);
182 }
184 }
185
186 // Write placeholder manual sections
187 manpage_fmt_write_section(f, "FILES");
188 fprintf(f, ".I ~/.ascii-chat/config.toml\n");
189 fprintf(f, "User configuration file\n");
191
192 manpage_fmt_write_section(f, "NOTES");
193 fprintf(f, "For more information and examples, visit the project repository.\n");
195
196 manpage_fmt_write_section(f, "BUGS");
197 fprintf(f, "Report bugs at the project issue tracker.\n");
199
200 manpage_fmt_write_section(f, "AUTHOR");
201 fprintf(f, "Contributed by the ascii-chat community.\n");
203
204 manpage_fmt_write_section(f, "SEE ALSO");
205 fprintf(f, ".B man(1),\n");
206 fprintf(f, ".B groff_man(7)\n");
208
209 if (should_close) {
210 fclose(f);
211 }
212
213 log_debug("Generated man page template to %s", output_path ? output_path : "stdout");
214 return ASCIICHAT_OK;
215}
216
218 const char *mode_name, const char *output_path,
219 const char *brief_description) {
220 if (!builder || !program_name || !brief_description) {
221 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing required parameters");
222 }
223
224 const options_config_t *config = options_builder_build(builder);
225 if (!config) {
226 return SET_ERRNO(ERROR_CONFIG, "Failed to build options configuration");
227 }
228
229 return options_config_generate_manpage_template(config, program_name, mode_name, output_path, brief_description);
230}
231
233 const char *mode_name, const char *output_path,
234 const char *brief_description) {
235 (void)mode_name; // Not used
236 (void)program_name; // Not used
237 (void)brief_description; // Not used
238
239 if (!config) {
240 return SET_ERRNO(ERROR_INVALID_PARAM, "config is required for man page generation");
241 }
242
243 FILE *f = NULL;
244 bool should_close = false;
245
246 if (output_path && strlen(output_path) > 0 && strcmp(output_path, "-") != 0) {
247 // Check if file already exists and prompt for confirmation
248 struct stat st;
249 if (stat(output_path, &st) == 0) {
250 // File exists - ask user if they want to overwrite
251 log_plain("Man page file already exists: %s", output_path);
252
253 bool overwrite = platform_prompt_yes_no("Overwrite", false); // Default to No
254 if (!overwrite) {
255 log_plain("Man page generation cancelled.");
256 return SET_ERRNO(ERROR_FILE_OPERATION, "User cancelled overwrite");
257 }
258
259 log_plain("Overwriting existing man page file...");
260 }
261
262 f = platform_fopen("file_stream", output_path, "w");
263 if (!f) {
264 return SET_ERRNO_SYS(ERROR_CONFIG, "Failed to open output file: %s", output_path);
265 }
266 should_close = true;
267 } else {
268 f = stdout;
269 }
270
271 // Load template resources
272 manpage_resources_t resources;
273 memset(&resources, 0, sizeof(resources));
275 if (err != ASCIICHAT_OK) {
276 if (should_close)
277 fclose(f);
278 return err;
279 }
280
281 if (!manpage_resources_is_valid(&resources)) {
282 if (should_close)
283 fclose(f);
284 manpage_resources_destroy(&resources);
285 return SET_ERRNO(ERROR_CONFIG, "Man page resources are not valid");
286 }
287
288 // Process template and merge AUTO sections with generated content
289 const char *template_content = resources.template_content;
290 const char *p = template_content;
291
292 bool in_auto_section = false;
293 char current_auto_section[128] = "";
294 bool found_section_header = false;
295
296 // Track MERGE sections
297 bool in_merge_section = false;
298 bool merge_content_generated = false;
299 char current_merge_section[64] = {0};
300
301 // For ENVIRONMENT MERGE section: collect manual variables
302 const char **manual_env_vars = NULL;
303 const char **manual_env_descs = NULL;
304 size_t manual_env_count = 0;
305 size_t manual_env_capacity = 0;
306
307 while (*p) {
308 // Find next line
309 const char *line_end = strchr(p, '\n');
310 if (!line_end) {
311 // Last line without newline
312 if (!in_auto_section && !in_merge_section) {
313 fputs(p, f);
314 }
315 break;
316 }
317
318 size_t line_len = (size_t)(line_end - p);
319
320 // Check for MERGE-START marker (only in current line)
321 bool has_merge_start = false;
322 if (strstr(p, "MERGE-START:") != NULL && strstr(p, "MERGE-START:") < line_end) {
323 has_merge_start = true;
324 }
325
326 if (has_merge_start) {
327 in_merge_section = true;
328 merge_content_generated = false;
329 manual_env_count = 0; // Reset manual variable collection
330 manual_env_capacity = 0;
331
332 // Extract section name (e.g., "ENVIRONMENT" from "MERGE-START: ENVIRONMENT")
333 const char *section_start = strstr(p, "MERGE-START:");
334 if (section_start && section_start < line_end) {
335 section_start += strlen("MERGE-START:");
336 while (*section_start && section_start < line_end && isspace(*section_start))
337 section_start++;
338
339 const char *section_name_end = section_start;
340 // Advance until we reach line_end (the newline position)
341 while (section_name_end < line_end) {
342 section_name_end++;
343 }
344
345 // Trim trailing whitespace
346 while (section_name_end > section_start && isspace(*(section_name_end - 1))) {
347 section_name_end--;
348 }
349
350 size_t section_name_len = (size_t)(section_name_end - section_start);
351 if (section_name_len > 0 && section_name_len < sizeof(current_merge_section)) {
352 // Use memcpy instead of SAFE_STRNCPY because source is not null-terminated
353 memcpy(current_merge_section, section_start, section_name_len);
354 current_merge_section[section_name_len] = '\0';
355 }
356 }
357
358 // Do NOT write the MERGE-START marker line - it's an internal control marker
359 // The content will be generated when MERGE-END is encountered
360
361 // For ENVIRONMENT MERGE sections, find and preserve the .SH header
362 // (skipping over any marker comment lines that might be in between)
363 if (strcmp(current_merge_section, "ENVIRONMENT") == 0) {
364 const char *search_line = line_end + 1;
365 bool found_header = false;
366 while (*search_line && !found_header) {
367 const char *search_line_end = strchr(search_line, '\n');
368 if (!search_line_end)
369 break;
370
371 // Check if this is the .SH header
372 if (strncmp(search_line, ".SH ", 4) == 0) {
373 // Write the .SH header line
374 size_t header_len = (size_t)(search_line_end - search_line);
375 fwrite(search_line, 1, header_len + 1, f);
376 p = search_line_end + 1;
377 found_header = true;
378 break;
379 }
380
381 // Skip marker comment lines (.\" MANUAL-*, .\" MERGE-*, etc)
382 if (strncmp(search_line, ".\\\" ", 4) == 0) {
383 search_line = search_line_end + 1;
384 continue; // Keep searching
385 }
386
387 // If we hit a non-marker, non-.SH line, stop searching
388 break;
389 }
390 if (found_header) {
391 continue; // Skip the "Skip to next line" at line 359
392 }
393 }
394
395 // Skip to next line
396 p = line_end + 1;
397 continue;
398
399 } else if (in_merge_section && strstr(p, "MERGE-END:") != NULL && strstr(p, "MERGE-END:") < line_end) {
400 // Before writing MERGE-END, generate content if not already done
401 if (!merge_content_generated) {
402 if (strcmp(current_merge_section, "ENVIRONMENT") == 0) {
403 log_debug("[MANPAGE] Generating ENVIRONMENT with %zu manual + %zu auto variables", manual_env_count,
404 config->num_descriptors);
405 char *env_content = manpage_content_generate_environment_with_manual(config, manual_env_vars,
406 manual_env_count, manual_env_descs);
407 if (env_content && *env_content != '\0') {
408 log_debug("[MANPAGE] Writing ENVIRONMENT content: %zu bytes", strlen(env_content));
409 fprintf(f, "%s", env_content);
410 } else {
411 log_warn("[MANPAGE] ENVIRONMENT content is empty!");
412 }
414 }
415 merge_content_generated = true;
416 }
417
418 in_merge_section = false;
419 // Do NOT write the MERGE-END marker line - it's an internal control marker
420 memset(current_merge_section, 0, sizeof(current_merge_section));
421
422 // Free collected manual variables (strings first, then arrays)
423 for (size_t i = 0; i < manual_env_count; i++) {
424 if (manual_env_vars && manual_env_vars[i]) {
425 char *var_name = (char *)manual_env_vars[i];
426 SAFE_FREE(var_name);
427 }
428 if (manual_env_descs && manual_env_descs[i]) {
429 char *var_desc = (char *)manual_env_descs[i];
430 SAFE_FREE(var_desc);
431 }
432 }
433 if (manual_env_vars) {
434 SAFE_FREE(manual_env_vars);
435 manual_env_vars = NULL;
436 }
437 if (manual_env_descs) {
438 SAFE_FREE(manual_env_descs);
439 manual_env_descs = NULL;
440 }
441 manual_env_count = 0;
442 manual_env_capacity = 0;
443
444 p = line_end + 1;
445 continue;
446
447 } else if (in_merge_section) {
448 // Within MERGE section: collect manual environment variables for ENVIRONMENT section
449 if (strcmp(current_merge_section, "ENVIRONMENT") == 0) {
450 // Check if line starts with ".TP" or ".B " (groff markers)
451 bool is_tp_marker = (line_len >= 3 && strncmp(p, ".TP", 3) == 0 && (line_len == 3 || isspace(p[3])));
452 bool is_b_marker = (line_len >= 3 && strncmp(p, ".B ", 3) == 0);
453
454 if (is_b_marker) {
455 // Extract variable name after ".B "
456 const char *var_start = p + 3;
457 while (*var_start && var_start < line_end && isspace(*var_start))
458 var_start++;
459
460 const char *var_end = var_start;
461 while (var_end < line_end && *var_end && *var_end != '\n') {
462 var_end++;
463 }
464
465 size_t var_len = (size_t)(var_end - var_start);
466 if (var_len > 0) {
467 char *var_name = SAFE_MALLOC(var_len + 1, char *);
468 memcpy(var_name, var_start, var_len);
469 var_name[var_len] = '\0';
470
471 // Trim trailing whitespace
472 while (var_len > 0 && isspace(var_name[var_len - 1])) {
473 var_name[--var_len] = '\0';
474 }
475
476 // Store the variable name
477 if (manual_env_count >= manual_env_capacity) {
478 manual_env_capacity = manual_env_capacity == 0 ? 16 : manual_env_capacity * 2;
479 manual_env_vars =
480 SAFE_REALLOC(manual_env_vars, manual_env_capacity * sizeof(const char *), const char **);
481 manual_env_descs =
482 SAFE_REALLOC(manual_env_descs, manual_env_capacity * sizeof(const char *), const char **);
483 }
484
485 manual_env_vars[manual_env_count] = var_name;
486 manual_env_descs[manual_env_count] = NULL; // Will be set from next line(s)
487 manual_env_count++;
488 }
489 } else if (manual_env_count > 0 && !is_tp_marker && !is_b_marker) {
490 // This is likely a description line following a ".B var_name" line
491 // Set description for the last collected variable
492 const char *desc_start = p;
493 while (*desc_start && desc_start < line_end && isspace(*desc_start))
494 desc_start++;
495
496 size_t desc_len = (size_t)(line_end - desc_start);
497 if (desc_len > 0 && manual_env_descs[manual_env_count - 1] == NULL) {
498 char *desc = SAFE_MALLOC(desc_len + 1, char *);
499 memcpy(desc, desc_start, desc_len);
500 desc[desc_len] = '\0';
501
502 // Trim trailing whitespace
503 while (desc_len > 0 && isspace(desc[desc_len - 1])) {
504 desc[--desc_len] = '\0';
505 }
506
507 if (desc_len > 0) {
508 manual_env_descs[manual_env_count - 1] = desc;
509 } else {
510 SAFE_FREE(desc);
511 }
512 }
513 }
514 // For ENVIRONMENT MERGE sections, do NOT write template content
515 // All content will be generated at MERGE-END
516 p = line_end + 1;
517 continue;
518 } else {
519 // For other MERGE sections (if any), write template content as-is
520 fwrite(p, 1, line_len + 1, f);
521 p = line_end + 1;
522 continue;
523 }
524 }
525
526 // Check for AUTO-START marker (only in current line)
527 bool has_auto_start = false;
528 const char *temp = p;
529 while (temp < line_end) {
530 if (strstr(temp, "AUTO-START:") != NULL) {
531 const char *found = strstr(temp, "AUTO-START:");
532 if (found < line_end) {
533 has_auto_start = true;
534 break;
535 }
536 // strstr() found it but it's beyond this line, so keep looking
537 temp = found + 1;
538 } else {
539 break;
540 }
541 }
542
543 if (has_auto_start) {
544 in_auto_section = true;
545 found_section_header = false;
546
547 // Extract section name (e.g., "SYNOPSIS" from "AUTO-START: SYNOPSIS")
548 const char *section_start = strstr(p, "AUTO-START:");
549 if (section_start && section_start < line_end) {
550 section_start += strlen("AUTO-START:");
551 while (*section_start && section_start < line_end && isspace(*section_start))
552 section_start++;
553
554 const char *section_name_end = section_start;
555 // Extract section name: everything from section_start until line_end
556 // Do NOT use a condition based on '\n' - just advance until we reach line_end
557 while (section_name_end < line_end) {
558 section_name_end++;
559 }
560
561 // Now trim trailing whitespace from the section name
562 while (section_name_end > section_start && isspace(*(section_name_end - 1))) {
563 section_name_end--;
564 }
565
566 size_t section_name_len = (size_t)(section_name_end - section_start);
567 if (section_name_len > 0 && section_name_len < sizeof(current_auto_section)) {
568 // Use memcpy instead of SAFE_STRNCPY because source is not null-terminated
569 // SAFE_STRNCPY uses strlcpy which only copies size-1 bytes
570 memcpy(current_auto_section, section_start, section_name_len);
571 current_auto_section[section_name_len] = '\0';
572 log_debug("[MANPAGE] Found AUTO-START section: '%s' (%zu bytes)", current_auto_section, section_name_len);
573 }
574 }
575
576 // Don't write the AUTO-START marker line - it's an internal control comment
577
578 } else if (strstr(p, "AUTO-END:") != NULL && strstr(p, "AUTO-END:") < line_end) {
579 in_auto_section = false;
580 // Don't write the AUTO-END marker line - it's an internal control comment
581 memset(current_auto_section, 0, sizeof(current_auto_section));
582 found_section_header = false;
583
584 } else if (in_auto_section) {
585 // Preserve comment lines that come before the section header
586 if (!found_section_header && strstr(p, ".\\\"") != NULL && strstr(p, ".\\\"") < line_end) {
587 // Skip comment lines in AUTO sections that contain "auto-generated" text
588 if (strstr(p, "auto-generated") == NULL) {
589 // Non-marker comment lines can be preserved
590 fwrite(p, 1, line_len + 1, f);
591 }
592 // Skip comment lines with "auto-generated" marker text
593 } else if (!found_section_header && strstr(p, ".SH ") != NULL && strstr(p, ".SH ") < line_end) {
594 // If this is the section header line (.SH ...), write it and generate content
595 fwrite(p, 1, line_len + 1, f);
596 found_section_header = true;
597
598 // Generate content for this AUTO section
599 if (strcmp(current_auto_section, "SYNOPSIS") == 0) {
600 log_debug("[MANPAGE] Generating SYNOPSIS section");
601 char *synopsis_content = NULL;
602 size_t synopsis_len = 0;
603 asciichat_error_t gen_err = manpage_merger_generate_synopsis(NULL, &synopsis_content, &synopsis_len);
604 log_debug("[MANPAGE] SYNOPSIS: err=%d, len=%zu", gen_err, synopsis_len);
605 if (gen_err == ASCIICHAT_OK && synopsis_content && synopsis_len > 0) {
606 fprintf(f, "%s", synopsis_content);
607 manpage_merger_free_content(synopsis_content);
608 }
609 } else if (strcmp(current_auto_section, "POSITIONAL ARGUMENTS") == 0) {
610 log_debug("[MANPAGE] Generating POSITIONAL ARGUMENTS (config has %zu args)", config->num_positional_args);
611 char *pos_content = manpage_content_generate_positional(config);
612 if (pos_content) {
613 size_t pos_len = strlen(pos_content);
614 log_debug("[MANPAGE] POSITIONAL ARGUMENTS: %zu bytes", pos_len);
615 if (*pos_content != '\0') {
616 fprintf(f, "%s", pos_content);
617 }
618 }
620 } else if (strcmp(current_auto_section, "USAGE") == 0) {
621 log_debug("[MANPAGE] Generating USAGE (config has %zu usage lines)", config->num_usage_lines);
622 char *usage_content = NULL;
623 size_t usage_len = 0;
624 asciichat_error_t usage_err = manpage_merger_generate_usage(config, &usage_content, &usage_len);
625 log_debug("[MANPAGE] USAGE: err=%d, len=%zu", usage_err, usage_len);
626 if (usage_err == ASCIICHAT_OK && usage_content && usage_len > 0) {
627 fprintf(f, "%s", usage_content);
628 manpage_merger_free_content(usage_content);
629 }
630 } else if (strcmp(current_auto_section, "EXAMPLES") == 0) {
631 log_debug("[MANPAGE] Generating EXAMPLES (config has %zu examples)", config->num_examples);
632 char *examples_content = manpage_content_generate_examples(config);
633 if (examples_content) {
634 size_t ex_len = strlen(examples_content);
635 log_debug("[MANPAGE] EXAMPLES: %zu bytes", ex_len);
636 if (*examples_content != '\0') {
637 fprintf(f, "%s", examples_content);
638 }
639 }
640 manpage_content_free_examples(examples_content);
641 } else if (strcmp(current_auto_section, "OPTIONS") == 0) {
642 log_debug("[MANPAGE] Generating OPTIONS (config has %zu descriptors)", config->num_descriptors);
643 char *options_content = manpage_content_generate_options(config);
644 if (options_content) {
645 size_t opt_len = strlen(options_content);
646 log_debug("[MANPAGE] OPTIONS: %zu bytes", opt_len);
647 if (*options_content != '\0') {
648 fprintf(f, "%s", options_content);
649 }
650 }
651 manpage_content_free_options(options_content);
652 }
653 }
654 // Otherwise skip old content between section header and AUTO-END
655 } else {
656 // Write all manual content (not in AUTO section)
657 // Skip marker comment lines: .\" AUTO-*, .\" MANUAL-*, .\" MERGE-*
658 // These are internal build-time control comments
659 bool is_marker_comment = (strstr(p, ".\\\" AUTO-") != NULL && strstr(p, ".\\\" AUTO-") < line_end) ||
660 (strstr(p, ".\\\" MANUAL-") != NULL && strstr(p, ".\\\" MANUAL-") < line_end) ||
661 (strstr(p, ".\\\" MERGE-") != NULL && strstr(p, ".\\\" MERGE-") < line_end);
662 if (!is_marker_comment) {
663 fwrite(p, 1, line_len + 1, f);
664 }
665 }
666
667 // Move to next line
668 p = line_end + 1;
669 }
670
671 manpage_resources_destroy(&resources);
672
673 fflush(f);
674 if (should_close) {
675 fclose(f);
676 }
677
678 log_debug("Generated merged man page to %s", output_path ? output_path : "stdout");
679 return ASCIICHAT_OK;
680}
681
682parsed_section_t *parse_manpage_sections(const char *filepath, size_t *num_sections) {
683 if (!filepath || !num_sections) {
684 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
685 return NULL;
686 }
687
688 FILE *f = platform_fopen("file_stream", filepath, "r");
689 if (!f) {
690 SET_ERRNO_SYS(ERROR_CONFIG, "Failed to open file: %s", filepath);
691 return NULL;
692 }
693
694 parsed_section_t *sections = NULL;
695 asciichat_error_t err = manpage_parser_parse_file(f, &sections, num_sections);
696 fclose(f);
697
698 if (err != ASCIICHAT_OK) {
699 return NULL;
700 }
701
702 return sections;
703}
704
705void free_parsed_sections(parsed_section_t *sections, size_t num_sections) {
706 manpage_parser_free_sections(sections, num_sections);
707}
708
709const parsed_section_t *find_section(const parsed_section_t *sections, size_t num_sections, const char *section_name) {
710 return manpage_parser_find_section(sections, num_sections, section_name);
711}
712
713#ifndef NDEBUG
714asciichat_error_t options_config_generate_final_manpage(const char *template_path, const char *output_path,
715 const char *version_string, const char *content_file_path) {
716 (void)template_path;
717 (void)output_path;
718 (void)version_string;
719 (void)content_file_path;
720 return SET_ERRNO(ERROR_CONFIG, "Not implemented in refactored version");
721}
722#endif
723
options_config_t * options_builder_build(options_builder_t *builder)
Build immutable options config.
Definition builder.c:482
Options builder API for flexible command-line option configuration.
⚙️ Common definitions, error codes, macros, and types shared throughout the application
char * manpage_content_generate_environment_with_manual(const options_config_t *config, const char **manual_vars, size_t manual_count, const char **manual_descs)
Generate ENVIRONMENT section with manual and auto variables.
Definition environment.c:74
void manpage_content_free_environment(char *content)
Free generated environment content.
char * manpage_content_generate_environment(const options_config_t *config)
Generate ENVIRONMENT VARIABLES section content.
Definition environment.c:14
ENVIRONMENT section generator for man pages.
char * manpage_content_generate_examples(const options_config_t *config)
Generate EXAMPLES section content.
Definition examples.c:79
void manpage_content_free_examples(char *content)
Free generated examples content.
Definition examples.c:147
EXAMPLES section generator for man pages.
void manpage_fmt_write_title(FILE *f, const char *program_name, const char *mode_name, const char *brief_description)
Write groff title/header (.TH directive)
Definition formatter.c:75
void manpage_fmt_write_section(FILE *f, const char *section_name)
Write a section header directive.
Definition formatter.c:28
void manpage_fmt_write_blank_line(FILE *f)
Write a blank line (for spacing between sections)
Definition formatter.c:35
Groff/troff formatting utilities for man page generation.
#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_FILE_OPERATION
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
#define log_plain(...)
Plain logging - writes to both log file and stderr without timestamps or log levels.
Definition log/log.h:618
const parsed_section_t * find_section(const parsed_section_t *sections, size_t num_sections, const char *section_name)
Find section by name.
const char * escape_groff_special(const char *str)
Escape special groff/troff characters in text.
asciichat_error_t options_config_generate_final_manpage(const char *template_path, const char *output_path, const char *version_string, const char *content_file_path)
Generate final man page (.1) from template (.1.in) with version substitution and optional content fil...
asciichat_error_t options_builder_generate_manpage_template(options_builder_t *builder, const char *program_name, const char *mode_name, const char *output_path, const char *brief_description)
Generate man page template from builder (before build())
asciichat_error_t options_config_generate_manpage_template(const options_config_t *config, const char *program_name, const char *mode_name, const char *output_path, const char *brief_description)
Generate a man page template from options builder configuration.
void free_parsed_sections(parsed_section_t *sections, size_t num_sections)
Free parsed sections array.
parsed_section_t * parse_manpage_sections(const char *filepath, size_t *num_sections)
Parse existing man page template to extract sections.
const char * format_mode_names(option_mode_bitmask_t mode_bitmask)
Format option mode names as human-readable string.
option_mode_bitmask_t
Option mode bitmask.
asciichat_error_t options_config_generate_manpage_merged(const options_config_t *config, const char *program_name, const char *mode_name, const char *output_path, const char *brief_description)
Generate merged man page template preserving manual content.
@ MODE_CLIENT
Client mode - network client options.
@ MODE_DISCOVERY_SERVICE
Discovery server mode - session management and WebRTC signaling.
@ MODE_SERVER
Server mode - network server options.
@ MODE_MIRROR
Mirror mode - local webcam viewing (no network)
@ MODE_DISCOVERY
Discovery mode - participant that can dynamically become host.
@ OPTION_MODE_BINARY
Binary-level options (parsed before mode detection)
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
bool platform_prompt_yes_no(const char *question, bool default_yes)
Prompt the user for a yes/no answer.
Definition util.c:83
FILE * platform_fopen(const char *name, const char *filename, const char *mode)
Safe file open stream (fopen replacement)
OPTIONS section generator for man pages.
char * manpage_content_generate_options(const options_config_t *config)
Generate OPTIONS section content.
void manpage_content_free_options(char *content)
Free generated options content.
📝 Logging API with multiple log levels and terminal output control
Man page template generation from options builder.
asciichat_error_t manpage_merger_generate_usage(const options_config_t *config, char **out_content, size_t *out_len)
Get auto-generated usage content.
Definition merger.c:79
void manpage_merger_free_content(char *content)
Free merged section content.
Definition merger.c:179
asciichat_error_t manpage_merger_generate_synopsis(const char *mode_name, char **out_content, size_t *out_len)
Get auto-generated synopsis content.
Definition merger.c:137
Man page content merging and orchestration.
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
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.
void manpage_content_free_positional(char *content)
Free generated positional content.
Definition positional.c:74
char * manpage_content_generate_positional(const options_config_t *config)
Generate POSITIONAL ARGUMENTS section content.
Definition positional.c:15
POSITIONAL ARGUMENTS section generator for man pages.
Cross-platform interactive prompting utilities.
void manpage_resources_destroy(manpage_resources_t *resources)
Cleanup allocated man page resources.
Definition resources.c:151
asciichat_error_t manpage_resources_load(manpage_resources_t *resources)
Load man page resources from embedded or filesystem sources.
Definition resources.c:107
bool manpage_resources_is_valid(const manpage_resources_t *resources)
Check if resources are available.
Definition resources.c:180
Resource management for man page generation.
Cross-platform stat and file type checking macros.
Man page resource container.
Definition resources.h:22
const char * template_content
Template file content (.1.in)
Definition resources.h:23
const char * env_var_name
Environment variable fallback (or NULL)
Definition builder.h:260
Options builder.
Definition builder.h:441
Options configuration.
Definition builder.h:401
size_t num_usage_lines
Number of usage lines.
Definition builder.h:417
option_descriptor_t * descriptors
Array of option descriptors.
Definition builder.h:402
size_t num_positional_args
Number of positional arguments.
Definition builder.h:409
size_t num_descriptors
Number of descriptors.
Definition builder.h:403
size_t num_examples
Number of examples.
Definition builder.h:420
Parsed section information.
Definition manpage.h:38
USAGE section generator for man pages.