ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
manpage.c File Reference

Man page generation using modular architecture. More...

Go to the source code of this file.

Functions

const char * escape_groff_special (const char *str)
 
const char * format_mode_names (option_mode_bitmask_t mode_bitmask)
 
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)
 
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)
 
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)
 
parsed_section_t * parse_manpage_sections (const char *filepath, size_t *num_sections)
 
void free_parsed_sections (parsed_section_t *sections, size_t num_sections)
 
const parsed_section_t * find_section (const parsed_section_t *sections, size_t num_sections, const char *section_name)
 
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)
 

Detailed Description

Man page generation using modular architecture.

Refactored to use modular layers:

  • Resources: Loading embedded/filesystem templates
  • Parser: Parsing existing man page sections
  • Formatter: Groff/troff formatting utilities
  • Content Generators: OPTIONS, ENVIRONMENT, USAGE, EXAMPLES, POSITIONAL
  • Merger: Intelligently merging auto-generated with manual content

Definition in file options/manpage.c.

Function Documentation

◆ escape_groff_special()

const char * escape_groff_special ( const char *  str)

◆ find_section()

const parsed_section_t * find_section ( const parsed_section_t *  sections,
size_t  num_sections,
const char *  section_name 
)

Definition at line 703 of file options/manpage.c.

703 {
704 return manpage_parser_find_section(sections, num_sections, section_name);
705}
const parsed_section_t * manpage_parser_find_section(const parsed_section_t *sections, size_t count, const char *section_name)
Definition parser.c:397

References manpage_parser_find_section().

◆ format_mode_names()

const char * format_mode_names ( option_mode_bitmask_t  mode_bitmask)

Definition at line 43 of file options/manpage.c.

43 {
44 if (mode_bitmask == 0) {
45 return NULL;
46 }
47
48 if ((mode_bitmask & OPTION_MODE_BINARY) && !(mode_bitmask & 0x1F)) {
49 return "global";
50 }
51
52 option_mode_bitmask_t user_modes_mask = (1 << MODE_SERVER) | (1 << MODE_CLIENT) | (1 << MODE_MIRROR) |
53 (1 << MODE_DISCOVERY_SERVICE) | (1 << MODE_DISCOVERY);
54 if ((mode_bitmask & user_modes_mask) == user_modes_mask) {
55 // If all user modes are set, check if binary-level is also set
56 if (mode_bitmask & OPTION_MODE_BINARY) {
57 return "global, client, server, mirror, discovery-service";
58 }
59 return "all modes";
60 }
61
62 static char mode_str[256];
63 mode_str[0] = '\0';
64 int pos = 0;
65
66 // Add global if binary-level is set
67 if (mode_bitmask & OPTION_MODE_BINARY) {
68 pos += safe_snprintf(mode_str + pos, sizeof(mode_str) - pos, "global");
69 }
70
71 // List modes in order: default, client, server, mirror, discovery-service
72 if (mode_bitmask & (1 << MODE_DISCOVERY)) {
73 pos += safe_snprintf(mode_str + pos, sizeof(mode_str) - pos, "%sdefault", pos > 0 ? ", " : "");
74 }
75 if (mode_bitmask & (1 << MODE_CLIENT)) {
76 pos += safe_snprintf(mode_str + pos, sizeof(mode_str) - pos, "%sclient", pos > 0 ? ", " : "");
77 }
78 if (mode_bitmask & (1 << MODE_SERVER)) {
79 pos += safe_snprintf(mode_str + pos, sizeof(mode_str) - pos, "%sserver", pos > 0 ? ", " : "");
80 }
81 if (mode_bitmask & (1 << MODE_MIRROR)) {
82 pos += safe_snprintf(mode_str + pos, sizeof(mode_str) - pos, "%smirror", pos > 0 ? ", " : "");
83 }
84 if (mode_bitmask & (1 << MODE_DISCOVERY_SERVICE)) {
85 pos += safe_snprintf(mode_str + pos, sizeof(mode_str) - pos, "%sdiscovery-service", pos > 0 ? ", " : "");
86 }
87
88 return pos > 0 ? mode_str : NULL;
89}
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456

References safe_snprintf().

Referenced by manpage_content_generate_options().

◆ free_parsed_sections()

void free_parsed_sections ( parsed_section_t *  sections,
size_t  num_sections 
)

Definition at line 699 of file options/manpage.c.

699 {
700 manpage_parser_free_sections(sections, num_sections);
701}
void manpage_parser_free_sections(parsed_section_t *sections, size_t count)
Definition parser.c:380

References manpage_parser_free_sections().

◆ options_builder_generate_manpage_template()

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 
)

Definition at line 218 of file options/manpage.c.

220 {
221 if (!builder || !program_name || !brief_description) {
222 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing required parameters");
223 }
224
225 const options_config_t *config = options_builder_build(builder);
226 if (!config) {
227 return SET_ERRNO(ERROR_CONFIG, "Failed to build options configuration");
228 }
229
230 return options_config_generate_manpage_template(config, program_name, mode_name, output_path, brief_description);
231}
options_config_t * options_builder_build(options_builder_t *builder)
Definition builder.c:452
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)

References options_builder_build(), and options_config_generate_manpage_template().

◆ options_config_generate_final_manpage()

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 
)

Definition at line 708 of file options/manpage.c.

709 {
710 (void)template_path;
711 (void)output_path;
712 (void)version_string;
713 (void)content_file_path;
714 return SET_ERRNO(ERROR_CONFIG, "Not implemented in refactored version");
715}

◆ options_config_generate_manpage_merged()

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 
)

Definition at line 233 of file options/manpage.c.

235 {
236 (void)mode_name; // Not used
237 (void)program_name; // Not used
238 (void)brief_description; // Not used
239
240 if (!config) {
241 return SET_ERRNO(ERROR_INVALID_PARAM, "config is required for man page generation");
242 }
243
244 FILE *f = NULL;
245 bool should_close = false;
246
247 if (output_path && strlen(output_path) > 0 && strcmp(output_path, "-") != 0) {
248 // Check if file already exists and prompt for confirmation
249 struct stat st;
250 if (stat(output_path, &st) == 0) {
251 // File exists - ask user if they want to overwrite
252 log_plain("Man page file already exists: %s", output_path);
253
254 bool overwrite = platform_prompt_yes_no("Overwrite", false); // Default to No
255 if (!overwrite) {
256 log_plain("Man page generation cancelled.");
257 return SET_ERRNO(ERROR_FILE_OPERATION, "User cancelled overwrite");
258 }
259
260 log_plain("Overwriting existing man page file...");
261 }
262
263 f = platform_fopen(output_path, "w");
264 if (!f) {
265 return SET_ERRNO_SYS(ERROR_CONFIG, "Failed to open output file: %s", output_path);
266 }
267 should_close = true;
268 } else {
269 f = stdout;
270 }
271
272 // Load template resources
273 manpage_resources_t resources;
274 memset(&resources, 0, sizeof(resources));
275 asciichat_error_t err = manpage_resources_load(&resources);
276 if (err != ASCIICHAT_OK) {
277 if (should_close)
278 fclose(f);
279 return err;
280 }
281
282 if (!manpage_resources_is_valid(&resources)) {
283 if (should_close)
284 fclose(f);
285 manpage_resources_destroy(&resources);
286 return SET_ERRNO(ERROR_CONFIG, "Man page resources are not valid");
287 }
288
289 // Process template and merge AUTO sections with generated content
290 const char *template_content = resources.template_content;
291 const char *p = template_content;
292
293 bool in_auto_section = false;
294 char current_auto_section[128] = "";
295 bool found_section_header = false;
296
297 // Track MERGE sections
298 bool in_merge_section = false;
299 bool merge_content_generated = false;
300 char current_merge_section[64] = {0};
301
302 // For ENVIRONMENT MERGE section: collect manual variables
303 const char **manual_env_vars = NULL;
304 const char **manual_env_descs = NULL;
305 size_t manual_env_count = 0;
306 size_t manual_env_capacity = 0;
307
308 while (*p) {
309 // Find next line
310 const char *line_end = strchr(p, '\n');
311 if (!line_end) {
312 // Last line without newline
313 if (!in_auto_section && !in_merge_section) {
314 fputs(p, f);
315 }
316 break;
317 }
318
319 size_t line_len = (size_t)(line_end - p);
320
321 // Check for MERGE-START marker (only in current line)
322 bool has_merge_start = false;
323 if (strstr(p, "MERGE-START:") != NULL && strstr(p, "MERGE-START:") < line_end) {
324 has_merge_start = true;
325 }
326
327 if (has_merge_start) {
328 in_merge_section = true;
329 merge_content_generated = false;
330 manual_env_count = 0; // Reset manual variable collection
331 manual_env_capacity = 0;
332
333 // Extract section name (e.g., "ENVIRONMENT" from "MERGE-START: ENVIRONMENT")
334 const char *section_start = strstr(p, "MERGE-START:");
335 if (section_start && section_start < line_end) {
336 section_start += strlen("MERGE-START:");
337 while (*section_start && section_start < line_end && isspace(*section_start))
338 section_start++;
339
340 const char *section_name_end = section_start;
341 while (section_name_end < line_end && *section_name_end && *section_name_end != '\n') {
342 section_name_end++;
343 }
344
345 while (section_name_end > section_start && isspace(*(section_name_end - 1))) {
346 section_name_end--;
347 }
348
349 size_t section_name_len = (size_t)(section_name_end - section_start);
350 if (section_name_len > 0 && section_name_len < sizeof(current_merge_section)) {
351 SAFE_STRNCPY(current_merge_section, section_start, section_name_len);
352 current_merge_section[section_name_len] = '\0';
353 }
354 }
355
356 // Do NOT write the MERGE-START marker line - it's an internal control marker
357 // The content will be generated when MERGE-END is encountered
358
359 // For ENVIRONMENT MERGE sections, find and preserve the .SH header
360 // (skipping over any marker comment lines that might be in between)
361 if (strcmp(current_merge_section, "ENVIRONMENT") == 0) {
362 const char *search_line = line_end + 1;
363 bool found_header = false;
364 while (*search_line && !found_header) {
365 const char *search_line_end = strchr(search_line, '\n');
366 if (!search_line_end)
367 break;
368
369 // Check if this is the .SH header
370 if (strncmp(search_line, ".SH ", 4) == 0) {
371 // Write the .SH header line
372 size_t header_len = (size_t)(search_line_end - search_line);
373 fwrite(search_line, 1, header_len + 1, f);
374 p = search_line_end + 1;
375 found_header = true;
376 break;
377 }
378
379 // Skip marker comment lines (.\" MANUAL-*, .\" MERGE-*, etc)
380 if (strncmp(search_line, ".\\\" ", 4) == 0) {
381 search_line = search_line_end + 1;
382 continue; // Keep searching
383 }
384
385 // If we hit a non-marker, non-.SH line, stop searching
386 break;
387 }
388 if (found_header) {
389 continue; // Skip the "Skip to next line" at line 359
390 }
391 }
392
393 // Skip to next line
394 p = line_end + 1;
395 continue;
396
397 } else if (in_merge_section && strstr(p, "MERGE-END:") != NULL && strstr(p, "MERGE-END:") < line_end) {
398 // Before writing MERGE-END, generate content if not already done
399 if (!merge_content_generated) {
400 if (strcmp(current_merge_section, "ENVIRONMENT") == 0) {
401 log_debug("[MANPAGE] Generating ENVIRONMENT with %zu manual + %zu auto variables", manual_env_count,
402 config->num_descriptors);
403 char *env_content = manpage_content_generate_environment_with_manual(config, manual_env_vars,
404 manual_env_count, manual_env_descs);
405 if (env_content && *env_content != '\0') {
406 log_debug("[MANPAGE] Writing ENVIRONMENT content: %zu bytes", strlen(env_content));
407 fprintf(f, "%s", env_content);
408 } else {
409 log_warn("[MANPAGE] ENVIRONMENT content is empty!");
410 }
412 }
413 merge_content_generated = true;
414 }
415
416 in_merge_section = false;
417 // Do NOT write the MERGE-END marker line - it's an internal control marker
418 memset(current_merge_section, 0, sizeof(current_merge_section));
419
420 // Free collected manual variables (strings first, then arrays)
421 for (size_t i = 0; i < manual_env_count; i++) {
422 if (manual_env_vars && manual_env_vars[i]) {
423 char *var_name = (char *)manual_env_vars[i];
424 SAFE_FREE(var_name);
425 }
426 if (manual_env_descs && manual_env_descs[i]) {
427 char *var_desc = (char *)manual_env_descs[i];
428 SAFE_FREE(var_desc);
429 }
430 }
431 if (manual_env_vars) {
432 SAFE_FREE(manual_env_vars);
433 manual_env_vars = NULL;
434 }
435 if (manual_env_descs) {
436 SAFE_FREE(manual_env_descs);
437 manual_env_descs = NULL;
438 }
439 manual_env_count = 0;
440 manual_env_capacity = 0;
441
442 p = line_end + 1;
443 continue;
444
445 } else if (in_merge_section) {
446 // Within MERGE section: collect manual environment variables for ENVIRONMENT section
447 if (strcmp(current_merge_section, "ENVIRONMENT") == 0) {
448 // Check if line starts with ".TP" or ".B " (groff markers)
449 bool is_tp_marker = (line_len >= 3 && strncmp(p, ".TP", 3) == 0 && (line_len == 3 || isspace(p[3])));
450 bool is_b_marker = (line_len >= 3 && strncmp(p, ".B ", 3) == 0);
451
452 if (is_b_marker) {
453 // Extract variable name after ".B "
454 const char *var_start = p + 3;
455 while (*var_start && var_start < line_end && isspace(*var_start))
456 var_start++;
457
458 const char *var_end = var_start;
459 while (var_end < line_end && *var_end && *var_end != '\n') {
460 var_end++;
461 }
462
463 size_t var_len = (size_t)(var_end - var_start);
464 if (var_len > 0) {
465 char *var_name = SAFE_MALLOC(var_len + 1, char *);
466 SAFE_STRNCPY(var_name, var_start, var_len);
467 var_name[var_len] = '\0';
468
469 // Trim trailing whitespace
470 while (var_len > 0 && isspace(var_name[var_len - 1])) {
471 var_name[--var_len] = '\0';
472 }
473
474 // Store the variable name
475 if (manual_env_count >= manual_env_capacity) {
476 manual_env_capacity = manual_env_capacity == 0 ? 16 : manual_env_capacity * 2;
477 manual_env_vars =
478 SAFE_REALLOC(manual_env_vars, manual_env_capacity * sizeof(const char *), const char **);
479 manual_env_descs =
480 SAFE_REALLOC(manual_env_descs, manual_env_capacity * sizeof(const char *), const char **);
481 }
482
483 manual_env_vars[manual_env_count] = var_name;
484 manual_env_descs[manual_env_count] = NULL; // Will be set from next line(s)
485 manual_env_count++;
486 }
487 } else if (manual_env_count > 0 && !is_tp_marker && !is_b_marker) {
488 // This is likely a description line following a ".B var_name" line
489 // Set description for the last collected variable
490 const char *desc_start = p;
491 while (*desc_start && desc_start < line_end && isspace(*desc_start))
492 desc_start++;
493
494 size_t desc_len = (size_t)(line_end - desc_start);
495 if (desc_len > 0 && manual_env_descs[manual_env_count - 1] == NULL) {
496 char *desc = SAFE_MALLOC(desc_len + 1, char *);
497 SAFE_STRNCPY(desc, desc_start, desc_len);
498 desc[desc_len] = '\0';
499
500 // Trim trailing whitespace
501 while (desc_len > 0 && isspace(desc[desc_len - 1])) {
502 desc[--desc_len] = '\0';
503 }
504
505 if (desc_len > 0) {
506 manual_env_descs[manual_env_count - 1] = desc;
507 } else {
508 SAFE_FREE(desc);
509 }
510 }
511 }
512 // For ENVIRONMENT MERGE sections, do NOT write template content
513 // All content will be generated at MERGE-END
514 p = line_end + 1;
515 continue;
516 } else {
517 // For other MERGE sections (if any), write template content as-is
518 fwrite(p, 1, line_len + 1, f);
519 p = line_end + 1;
520 continue;
521 }
522 }
523
524 // Check for AUTO-START marker (only in current line)
525 bool has_auto_start = false;
526 const char *temp = p;
527 while (temp < line_end) {
528 if (strstr(temp, "AUTO-START:") != NULL) {
529 const char *found = strstr(temp, "AUTO-START:");
530 if (found < line_end) {
531 has_auto_start = true;
532 break;
533 }
534 // strstr() found it but it's beyond this line, so keep looking
535 temp = found + 1;
536 } else {
537 break;
538 }
539 }
540
541 if (has_auto_start) {
542 in_auto_section = true;
543 found_section_header = false;
544
545 // Extract section name (e.g., "SYNOPSIS" from "AUTO-START: SYNOPSIS")
546 const char *section_start = strstr(p, "AUTO-START:");
547 if (section_start && section_start < line_end) {
548 section_start += strlen("AUTO-START:");
549 while (*section_start && section_start < line_end && isspace(*section_start))
550 section_start++;
551
552 const char *section_name_end = section_start;
553 // Extract section name: everything until end of line, then trim trailing whitespace
554 while (section_name_end < line_end && *section_name_end && *section_name_end != '\n') {
555 section_name_end++;
556 }
557
558 // Now trim trailing whitespace from the section name
559 while (section_name_end > section_start && isspace(*(section_name_end - 1))) {
560 section_name_end--;
561 }
562
563 size_t section_name_len = (size_t)(section_name_end - section_start);
564 if (section_name_len > 0 && section_name_len < sizeof(current_auto_section)) {
565 SAFE_STRNCPY(current_auto_section, section_start, section_name_len);
566 current_auto_section[section_name_len] = '\0';
567 }
568 }
569
570 // Don't write the AUTO-START marker line - it's an internal control comment
571
572 } else if (strstr(p, "AUTO-END:") != NULL && strstr(p, "AUTO-END:") < line_end) {
573 in_auto_section = false;
574 // Don't write the AUTO-END marker line - it's an internal control comment
575 memset(current_auto_section, 0, sizeof(current_auto_section));
576 found_section_header = false;
577
578 } else if (in_auto_section) {
579 // Preserve comment lines that come before the section header
580 if (!found_section_header && strstr(p, ".\\\"") != NULL && strstr(p, ".\\\"") < line_end) {
581 // Skip comment lines in AUTO sections that contain "auto-generated" text
582 if (strstr(p, "auto-generated") == NULL) {
583 // Non-marker comment lines can be preserved
584 fwrite(p, 1, line_len + 1, f);
585 }
586 // Skip comment lines with "auto-generated" marker text
587 } else if (!found_section_header && strstr(p, ".SH ") != NULL && strstr(p, ".SH ") < line_end) {
588 // If this is the section header line (.SH ...), write it and generate content
589 fwrite(p, 1, line_len + 1, f);
590 found_section_header = true;
591
592 // Generate content for this AUTO section
593 if (strcmp(current_auto_section, "SYNOPSIS") == 0) {
594 log_debug("[MANPAGE] Generating SYNOPSIS section");
595 char *synopsis_content = NULL;
596 size_t synopsis_len = 0;
597 asciichat_error_t gen_err = manpage_merger_generate_synopsis(NULL, &synopsis_content, &synopsis_len);
598 log_debug("[MANPAGE] SYNOPSIS: err=%d, len=%zu", gen_err, synopsis_len);
599 if (gen_err == ASCIICHAT_OK && synopsis_content && synopsis_len > 0) {
600 fprintf(f, "%s", synopsis_content);
601 manpage_merger_free_content(synopsis_content);
602 }
603 } else if (strcmp(current_auto_section, "POSITIONAL ARGUMENTS") == 0) {
604 log_debug("[MANPAGE] Generating POSITIONAL ARGUMENTS (config has %zu args)", config->num_positional_args);
605 char *pos_content = manpage_content_generate_positional(config);
606 if (pos_content) {
607 size_t pos_len = strlen(pos_content);
608 log_debug("[MANPAGE] POSITIONAL ARGUMENTS: %zu bytes", pos_len);
609 if (*pos_content != '\0') {
610 fprintf(f, "%s", pos_content);
611 }
612 }
614 } else if (strcmp(current_auto_section, "USAGE") == 0) {
615 log_debug("[MANPAGE] Generating USAGE (config has %zu usage lines)", config->num_usage_lines);
616 char *usage_content = NULL;
617 size_t usage_len = 0;
618 asciichat_error_t usage_err = manpage_merger_generate_usage(config, &usage_content, &usage_len);
619 log_debug("[MANPAGE] USAGE: err=%d, len=%zu", usage_err, usage_len);
620 if (usage_err == ASCIICHAT_OK && usage_content && usage_len > 0) {
621 fprintf(f, "%s", usage_content);
622 manpage_merger_free_content(usage_content);
623 }
624 } else if (strcmp(current_auto_section, "EXAMPLES") == 0) {
625 log_debug("[MANPAGE] Generating EXAMPLES (config has %zu examples)", config->num_examples);
626 char *examples_content = manpage_content_generate_examples(config);
627 if (examples_content) {
628 size_t ex_len = strlen(examples_content);
629 log_debug("[MANPAGE] EXAMPLES: %zu bytes", ex_len);
630 if (*examples_content != '\0') {
631 fprintf(f, "%s", examples_content);
632 }
633 }
634 manpage_content_free_examples(examples_content);
635 } else if (strcmp(current_auto_section, "OPTIONS") == 0) {
636 log_debug("[MANPAGE] Generating OPTIONS (config has %zu descriptors)", config->num_descriptors);
637 char *options_content = manpage_content_generate_options(config);
638 if (options_content) {
639 size_t opt_len = strlen(options_content);
640 log_debug("[MANPAGE] OPTIONS: %zu bytes", opt_len);
641 if (*options_content != '\0') {
642 fprintf(f, "%s", options_content);
643 }
644 }
645 manpage_content_free_options(options_content);
646 }
647 }
648 // Otherwise skip old content between section header and AUTO-END
649 } else {
650 // Write all manual content (not in AUTO section)
651 // Skip marker comment lines: .\" AUTO-*, .\" MANUAL-*, .\" MERGE-*
652 // These are internal build-time control comments
653 bool is_marker_comment = (strstr(p, ".\\\" AUTO-") != NULL && strstr(p, ".\\\" AUTO-") < line_end) ||
654 (strstr(p, ".\\\" MANUAL-") != NULL && strstr(p, ".\\\" MANUAL-") < line_end) ||
655 (strstr(p, ".\\\" MERGE-") != NULL && strstr(p, ".\\\" MERGE-") < line_end);
656 if (!is_marker_comment) {
657 fwrite(p, 1, line_len + 1, f);
658 }
659 }
660
661 // Move to next line
662 p = line_end + 1;
663 }
664
665 manpage_resources_destroy(&resources);
666
667 fflush(f);
668 if (should_close) {
669 fclose(f);
670 }
671
672 log_debug("Generated merged man page to %s", output_path ? output_path : "stdout");
673 return ASCIICHAT_OK;
674}
char * manpage_content_generate_environment_with_manual(const options_config_t *config, const char **manual_vars, size_t manual_count, const char **manual_descs)
Definition environment.c:74
void manpage_content_free_environment(char *content)
char * manpage_content_generate_examples(const options_config_t *config)
Definition examples.c:79
void manpage_content_free_examples(char *content)
Definition examples.c:141
char * manpage_content_generate_options(const options_config_t *config)
void manpage_content_free_options(char *content)
asciichat_error_t manpage_merger_generate_usage(const options_config_t *config, char **out_content, size_t *out_len)
Definition merger.c:79
void manpage_merger_free_content(char *content)
Definition merger.c:179
asciichat_error_t manpage_merger_generate_synopsis(const char *mode_name, char **out_content, size_t *out_len)
Definition merger.c:137
void manpage_content_free_positional(char *content)
Definition positional.c:64
char * manpage_content_generate_positional(const options_config_t *config)
Definition positional.c:15
void manpage_resources_destroy(manpage_resources_t *resources)
Definition resources.c:151
asciichat_error_t manpage_resources_load(manpage_resources_t *resources)
Definition resources.c:107
bool manpage_resources_is_valid(const manpage_resources_t *resources)
Definition resources.c:180
bool platform_prompt_yes_no(const char *question, bool default_yes)
Definition util.c:81
FILE * platform_fopen(const char *filename, const char *mode)

References manpage_content_free_environment(), manpage_content_free_examples(), manpage_content_free_options(), manpage_content_free_positional(), manpage_content_generate_environment_with_manual(), manpage_content_generate_examples(), manpage_content_generate_options(), manpage_content_generate_positional(), manpage_merger_free_content(), manpage_merger_generate_synopsis(), manpage_merger_generate_usage(), manpage_resources_destroy(), manpage_resources_is_valid(), manpage_resources_load(), platform_fopen(), and platform_prompt_yes_no().

Referenced by action_create_manpage(), and options_init().

◆ options_config_generate_manpage_template()

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 
)

Definition at line 95 of file options/manpage.c.

97 {
98 if (!config || !program_name || !brief_description) {
99 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing required parameters for man page generation");
100 }
101
102 FILE *f = NULL;
103 bool should_close = false;
104
105 if (output_path) {
106 f = platform_fopen(output_path, "w");
107 if (!f) {
108 return SET_ERRNO_SYS(ERROR_CONFIG, "Failed to open output file: %s", output_path);
109 }
110 should_close = true;
111 } else {
112 f = stdout;
113 }
114
115 // Write title/header
116 manpage_fmt_write_title(f, program_name, mode_name, brief_description);
117
118 // Write SYNOPSIS
119 char *synopsis_content = NULL;
120 size_t synopsis_len = 0;
121 asciichat_error_t err = manpage_merger_generate_synopsis(mode_name, &synopsis_content, &synopsis_len);
122 if (err == ASCIICHAT_OK && synopsis_content && synopsis_len > 0) {
123 fprintf(f, "%s", synopsis_content);
124 manpage_merger_free_content(synopsis_content);
125 }
126
127 // Write POSITIONAL ARGUMENTS if present
128 if (config->num_positional_args > 0) {
129 char *pos_content = manpage_content_generate_positional(config);
130 if (pos_content && *pos_content != '\0') {
131 fprintf(f, "%s", pos_content);
132 }
134 }
135
136 // Write DESCRIPTION
137 manpage_fmt_write_section(f, "DESCRIPTION");
138 fprintf(f, ".B ascii-chat\nis a terminal-based video chat application that converts webcam video to ASCII\n");
139 fprintf(f, "art in real-time. It enables video chat directly in your terminal, whether you're\n");
140 fprintf(f, "using a local console, a remote SSH session, or any terminal emulator.\n");
142
143 // Write USAGE
144 char *usage_content = NULL;
145 size_t usage_len = 0;
146 err = manpage_merger_generate_usage(config, &usage_content, &usage_len);
147 if (err == ASCIICHAT_OK && usage_content && usage_len > 0) {
148 fprintf(f, ".SH USAGE\n%s", usage_content);
149 manpage_merger_free_content(usage_content);
150 }
151
152 // Write OPTIONS
153 if (config->num_descriptors > 0) {
154 char *options_content = manpage_content_generate_options(config);
155 if (options_content && *options_content != '\0') {
156 fprintf(f, "%s", options_content);
157 }
158 manpage_content_free_options(options_content);
159 }
160
161 // Write EXAMPLES if present
162 if (config->num_examples > 0) {
163 char *examples_content = manpage_content_generate_examples(config);
164 if (examples_content && *examples_content != '\0') {
165 fprintf(f, "%s", examples_content);
166 }
167 manpage_content_free_examples(examples_content);
168 }
169
170 // Write ENVIRONMENT if present
171 bool has_env_vars = false;
172 for (size_t i = 0; i < config->num_descriptors; i++) {
173 if (config->descriptors[i].env_var_name) {
174 has_env_vars = true;
175 break;
176 }
177 }
178
179 if (has_env_vars) {
180 char *env_content = manpage_content_generate_environment(config);
181 if (env_content && *env_content != '\0') {
182 fprintf(f, "%s", env_content);
183 }
185 }
186
187 // Write placeholder manual sections
188 manpage_fmt_write_section(f, "FILES");
189 fprintf(f, ".I ~/.ascii-chat/config.toml\n");
190 fprintf(f, "User configuration file\n");
192
193 manpage_fmt_write_section(f, "NOTES");
194 fprintf(f, "For more information and examples, visit the project repository.\n");
196
197 manpage_fmt_write_section(f, "BUGS");
198 fprintf(f, "Report bugs at the project issue tracker.\n");
200
201 manpage_fmt_write_section(f, "AUTHOR");
202 fprintf(f, "Contributed by the ascii-chat community.\n");
204
205 manpage_fmt_write_section(f, "SEE ALSO");
206 fprintf(f, ".B man(1),\n");
207 fprintf(f, ".B groff_man(7)\n");
209
210 if (should_close) {
211 fclose(f);
212 }
213
214 log_debug("Generated man page template to %s", output_path ? output_path : "stdout");
215 return ASCIICHAT_OK;
216}
char * manpage_content_generate_environment(const options_config_t *config)
Definition environment.c:14
void manpage_fmt_write_title(FILE *f, const char *program_name, const char *mode_name, const char *brief_description)
Definition formatter.c:75
void manpage_fmt_write_section(FILE *f, const char *section_name)
Definition formatter.c:28
void manpage_fmt_write_blank_line(FILE *f)
Definition formatter.c:35

References manpage_content_free_environment(), manpage_content_free_examples(), manpage_content_free_options(), manpage_content_free_positional(), manpage_content_generate_environment(), manpage_content_generate_examples(), manpage_content_generate_options(), manpage_content_generate_positional(), manpage_fmt_write_blank_line(), manpage_fmt_write_section(), manpage_fmt_write_title(), manpage_merger_free_content(), manpage_merger_generate_synopsis(), manpage_merger_generate_usage(), and platform_fopen().

Referenced by options_builder_generate_manpage_template().

◆ parse_manpage_sections()

parsed_section_t * parse_manpage_sections ( const char *  filepath,
size_t *  num_sections 
)

Definition at line 676 of file options/manpage.c.

676 {
677 if (!filepath || !num_sections) {
678 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
679 return NULL;
680 }
681
682 FILE *f = platform_fopen(filepath, "r");
683 if (!f) {
684 SET_ERRNO_SYS(ERROR_CONFIG, "Failed to open file: %s", filepath);
685 return NULL;
686 }
687
688 parsed_section_t *sections = NULL;
689 asciichat_error_t err = manpage_parser_parse_file(f, &sections, num_sections);
690 fclose(f);
691
692 if (err != ASCIICHAT_OK) {
693 return NULL;
694 }
695
696 return sections;
697}
asciichat_error_t manpage_parser_parse_file(FILE *f, parsed_section_t **out_sections, size_t *out_count)
Definition parser.c:339

References manpage_parser_parse_file(), and platform_fopen().