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

Custom option parsers implementation. More...

Go to the source code of this file.

Data Structures

struct  setting_map_entry_t
 Lookup table for setting string-to-enum mapping. More...
 

Functions

bool parse_color_setting (const char *arg, void *dest, char **error_msg)
 
bool parse_utf8_setting (const char *arg, void *dest, char **error_msg)
 
bool parse_color_mode (const char *arg, void *dest, char **error_msg)
 
bool parse_color_filter (const char *arg, void *dest, char **error_msg)
 
bool parse_render_mode (const char *arg, void *dest, char **error_msg)
 
bool parse_palette_type (const char *arg, void *dest, char **error_msg)
 
bool parse_log_level (const char *arg, void *dest, char **error_msg)
 
bool parse_port_option (const char *arg, void *dest, char **error_msg)
 
int parse_server_bind_address (const char *arg, void *config, char **remaining, int num_remaining, char **error_msg)
 Parse server bind address positional argument.
 
int parse_client_address (const char *arg, void *config, char **remaining, int num_remaining, char **error_msg)
 Parse client address positional argument.
 
bool parse_palette_chars (const char *arg, void *dest, char **error_msg)
 
bool parse_verbose_flag (const char *arg, void *dest, char **error_msg)
 
bool parse_timestamp (const char *arg, void *dest, char **error_msg)
 Custom parser for –seek flag.
 
bool parse_volume (const char *arg, void *dest, char **error_msg)
 
bool parse_log_file (const char *arg, void *dest, char **error_msg)
 
bool parse_audio_source (const char *arg, void *dest, char **error_msg)
 

Detailed Description

Custom option parsers implementation.

Definition in file parsers.c.

Function Documentation

◆ parse_audio_source()

bool parse_audio_source ( const char *  arg,
void *  dest,
char **  error_msg 
)

Definition at line 1041 of file parsers.c.

1041 {
1042 if (!arg || !dest) {
1043 if (error_msg) {
1044 *error_msg = platform_strdup("Internal error: NULL argument or destination");
1045 }
1046 return false;
1047 }
1048
1049 audio_source_t *audio_source = (audio_source_t *)dest;
1050 char lower[32];
1051 to_lower(arg, lower, sizeof(lower));
1052
1053 // Auto (smart selection based on media state)
1054 if (strcmp(lower, "auto") == 0) {
1055 *audio_source = AUDIO_SOURCE_AUTO;
1056 return true;
1057 }
1058
1059 // Microphone only
1060 if (strcmp(lower, "mic") == 0) {
1061 *audio_source = AUDIO_SOURCE_MIC;
1062 return true;
1063 }
1064
1065 // Media only
1066 if (strcmp(lower, "media") == 0) {
1067 *audio_source = AUDIO_SOURCE_MEDIA;
1068 return true;
1069 }
1070
1071 // Both microphone and media
1072 if (strcmp(lower, "both") == 0) {
1073 *audio_source = AUDIO_SOURCE_BOTH;
1074 return true;
1075 }
1076
1077 if (error_msg) {
1078 *error_msg = platform_strdup("Audio source must be 'auto', 'mic', 'media', or 'both'");
1079 }
1080 return false;
1081}
char * platform_strdup(const char *s)

References platform_strdup().

◆ parse_client_address()

int parse_client_address ( const char *  arg,
void *  config,
char **  remaining,
int  num_remaining,
char **  error_msg 
)

Parse client address positional argument.

Implements the client address parsing logic from client.c. Parses [address][:port] with complex IPv6 handling.

Definition at line 634 of file parsers.c.

634 {
635 (void)remaining;
636 (void)num_remaining;
637
638 if (!arg || !config) {
639 if (error_msg) {
640 *error_msg = platform_strdup("Internal error: NULL argument or config");
641 }
642 return -1;
643 }
644
645 log_debug("parse_client_address: Processing argument: '%s'", arg);
646
647 // Access address and port fields from options_state struct
648 char *address = (char *)config + offsetof(struct options_state, address);
649 int *port = (int *)((char *)config + offsetof(struct options_state, port));
650
651 // Check for WebSocket URL (ws:// or wss://) FIRST before session string validation
652 // WebSocket URLs are passed through without validation or port extraction
653 if (strncmp(arg, "ws://", 5) == 0 || strncmp(arg, "wss://", 6) == 0) {
654 log_debug("Detected WebSocket URL: %s", arg);
655 SAFE_SNPRINTF(address, OPTIONS_BUFF_SIZE, "%s", arg);
656 // For wss://, default to no ACIP-level encryption (TLS handles it)
657 if (strncmp(arg, "wss://", 6) == 0) {
658 bool *no_encrypt = (bool *)config + offsetof(struct options_state, no_encrypt);
659 *no_encrypt = true;
660 log_debug("Auto-detected wss:// - setting no_encrypt=true (TLS handles encryption)");
661 }
662 // Don't set port - WebSocket transport handles URL parsing internally
663 return 1; // Consumed 1 argument
664 }
665
666 // Check if this is a session string (format: adjective-noun-noun)
667 // Session strings have exactly 2 hyphens, only lowercase letters, length 5-47
668 bool is_session = is_session_string(arg);
669 log_debug("parse_client_address: is_session_string('%s') = %s", arg, is_session ? "true" : "false");
670
671 if (is_session) {
672 // This is a session string, not a server address
673 char *session_string = (char *)config + offsetof(struct options_state, session_string);
674 SAFE_SNPRINTF(session_string, SESSION_STRING_BUFFER_SIZE, "%s", arg);
675 log_debug("parse_client_address: Stored session string: %s", arg);
676 return 1; // Consumed 1 arg
677 }
678
679 // Not a session string, parse as server address
680 log_debug("parse_client_address: Parsing as server address (not a session string)");
681
682 // Check for port in address (format: address:port or [ipv6]:port)
683 const char *colon = strrchr(arg, ':');
684
685 if (colon != NULL) {
686 // Check if this is IPv6 with port [::1]:port or plain hostname:port
687 if (arg[0] == '[') {
688 // IPv6 with brackets: [address]:port
689 const char *closing_bracket = strchr(arg, ']');
690 if (closing_bracket && closing_bracket < colon) {
691 // Extract address (remove brackets)
692 size_t addr_len = (size_t)(closing_bracket - arg - 1);
693 if (addr_len >= OPTIONS_BUFF_SIZE) {
694 if (error_msg) {
695 *error_msg = platform_strdup("IPv6 address too long");
696 }
697 return -1;
698 }
699 SAFE_SNPRINTF(address, OPTIONS_BUFF_SIZE, "%.*s", (int)addr_len, arg + 1);
700
701 // Extract and validate port
702 const char *port_str = colon + 1;
703 char *endptr;
704 long port_num = strtol(port_str, &endptr, 10);
705 if (*endptr != '\0' || port_num < 1 || port_num > 65535) {
706 if (error_msg) {
707 char msg[256];
708 safe_snprintf(msg, sizeof(msg), "Invalid port number '%s'. Must be 1-65535.", port_str);
709 *error_msg = platform_strdup(msg);
710 }
711 return -1;
712 }
713 *port = (int)port_num;
714 }
715 } else {
716 // Check if it's IPv6 without brackets (no port allowed)
717 // or hostname/IPv4:port
718 size_t colon_count = 0;
719 for (const char *p = arg; *p; p++) {
720 if (*p == ':')
721 colon_count++;
722 }
723
724 if (colon_count == 1) {
725 // Likely hostname:port or IPv4:port
726 size_t addr_len = (size_t)(colon - arg);
727 if (addr_len >= OPTIONS_BUFF_SIZE) {
728 if (error_msg) {
729 *error_msg = platform_strdup("Address too long");
730 }
731 return -1;
732 }
733 SAFE_SNPRINTF(address, OPTIONS_BUFF_SIZE, "%.*s", (int)addr_len, arg);
734
735 // Extract and validate port
736 const char *port_str = colon + 1;
737 char *endptr;
738 long port_num = strtol(port_str, &endptr, 10);
739 if (*endptr != '\0' || port_num < 1 || port_num > 65535) {
740 if (error_msg) {
741 char msg[256];
742 safe_snprintf(msg, sizeof(msg), "Invalid port number '%s'. Must be 1-65535.", port_str);
743 *error_msg = platform_strdup(msg);
744 }
745 return -1;
746 }
747 *port = (int)port_num;
748 } else {
749 // Multiple colons - likely bare IPv6 address
750 SAFE_SNPRINTF(address, OPTIONS_BUFF_SIZE, "%s", arg);
751 }
752 }
753 } else {
754 // No colon - just an address
755 SAFE_SNPRINTF(address, OPTIONS_BUFF_SIZE, "%s", arg);
756 }
757
758 // Validate addresses using comprehensive IPv4/IPv6 detection
759 bool has_dot = strchr(address, '.') != NULL;
760 bool has_colon = strchr(address, ':') != NULL;
761 bool starts_with_digit = address[0] >= '0' && address[0] <= '9';
762
763 // Potential IPv6 address (has colons) - validate as IPv6
764 if (has_colon) {
765 if (!is_valid_ipv6(address)) {
766 if (error_msg) {
767 char msg[512];
768 safe_snprintf(msg, sizeof(msg),
769 "Invalid IPv6 address '%s'.\n"
770 "IPv6 addresses must be valid hex notation with colons.\n"
771 "Examples: ::1, 2001:db8::1, fe80::1\n"
772 "Or use hostnames like example.com",
773 address);
774 *error_msg = platform_strdup(msg);
775 }
776 return -1;
777 }
778 } else if (has_dot && starts_with_digit) {
779 // Potential IPv4 address (has dots and starts with digit) - validate strictly
780 if (!is_valid_ipv4(address)) {
781 if (error_msg) {
782 char msg[512];
783 safe_snprintf(msg, sizeof(msg),
784 "Invalid IPv4 address '%s'.\n"
785 "IPv4 addresses must have exactly 4 octets (0-255) separated by dots.\n"
786 "Examples: 127.0.0.1, 192.168.1.1\n"
787 "For hostnames, use letters: example.com, localhost",
788 address);
789 *error_msg = platform_strdup(msg);
790 }
791 return -1;
792 }
793 }
794 // Otherwise treat as valid hostname (no validation needed)
795
796 // Note: Port conflict checking would require additional state
797 // (checking if --port flag was used). For now, this is a simplified version.
798 // Full implementation would need to track whether port was set via flag.
799
800 log_debug("parse_client_address: Set address='%s', port=%d", address[0] ? address : "(empty)", *port);
801
802 return 1; // Consumed 1 arg
803}
bool is_session_string(const char *str)
int is_valid_ipv4(const char *ip)
Definition ip.c:58
int is_valid_ipv6(const char *ip)
Definition ip.c:105
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456

References is_session_string(), is_valid_ipv4(), is_valid_ipv6(), platform_strdup(), and safe_snprintf().

Referenced by options_preset_unified().

◆ parse_color_filter()

bool parse_color_filter ( const char *  arg,
void *  dest,
char **  error_msg 
)

Definition at line 270 of file parsers.c.

270 {
271 if (!arg || !dest) {
272 if (error_msg) {
273 *error_msg = platform_strdup("Internal error: NULL argument or destination");
274 }
275 return false;
276 }
277
278 color_filter_t *color_filter = (color_filter_t *)dest;
279 char lower[32];
280 to_lower(arg, lower, sizeof(lower));
281
282 // Try to match against all known color filters
283 *color_filter = color_filter_from_cli_name(lower);
284 if (*color_filter != COLOR_FILTER_NONE || strcmp(lower, "none") == 0) {
285 return true;
286 }
287
288 // Invalid value
289 if (error_msg) {
290 char msg[256];
291 safe_snprintf(msg, sizeof(msg),
292 "Invalid color filter '%s'. Valid values: none, black, white, green, magenta, fuchsia, "
293 "orange, teal, cyan, pink, red, yellow",
294 arg);
295 *error_msg = platform_strdup(msg);
296 }
297 return false;
298}
color_filter_t color_filter_from_cli_name(const char *cli_name)

References color_filter_from_cli_name(), platform_strdup(), and safe_snprintf().

◆ parse_color_mode()

bool parse_color_mode ( const char *  arg,
void *  dest,
char **  error_msg 
)

Definition at line 211 of file parsers.c.

211 {
212 if (!arg || !dest) {
213 if (error_msg) {
214 *error_msg = platform_strdup("Internal error: NULL argument or destination");
215 }
216 return false;
217 }
218
219 terminal_color_mode_t *color_mode = (terminal_color_mode_t *)dest;
220 char lower[32];
221 to_lower(arg, lower, sizeof(lower));
222
223 // Auto-detect
224 if (strcmp(lower, "auto") == 0 || strcmp(lower, "a") == 0) {
225 *color_mode = TERM_COLOR_AUTO;
226 return true;
227 }
228
229 // Monochrome/None
230 if (strcmp(lower, "none") == 0 || strcmp(lower, "mono") == 0 || strcmp(lower, "monochrome") == 0 ||
231 strcmp(lower, "0") == 0) {
232 *color_mode = TERM_COLOR_NONE;
233 return true;
234 }
235
236 // 16-color
237 if (strcmp(lower, "16") == 0 || strcmp(lower, "16color") == 0 || strcmp(lower, "ansi") == 0 ||
238 strcmp(lower, "1") == 0) {
239 *color_mode = TERM_COLOR_16;
240 return true;
241 }
242
243 // 256-color
244 if (strcmp(lower, "256") == 0 || strcmp(lower, "256color") == 0 || strcmp(lower, "2") == 0) {
245 *color_mode = TERM_COLOR_256;
246 return true;
247 }
248
249 // Truecolor
250 if (strcmp(lower, "truecolor") == 0 || strcmp(lower, "true") == 0 || strcmp(lower, "tc") == 0 ||
251 strcmp(lower, "rgb") == 0 || strcmp(lower, "24bit") == 0 || strcmp(lower, "3") == 0) {
252 *color_mode = TERM_COLOR_TRUECOLOR;
253 return true;
254 }
255
256 // Invalid value - suggest closest match
257 if (error_msg) {
258 char msg[256];
259 const char *suggestion = asciichat_suggest_enum_value("color-mode", arg);
260 if (suggestion) {
261 safe_snprintf(msg, sizeof(msg), "Invalid color mode '%s'. Did you mean '%s'?", arg, suggestion);
262 } else {
263 safe_snprintf(msg, sizeof(msg), "Invalid color mode '%s'. Valid values: auto, none, 16, 256, truecolor", arg);
264 }
265 *error_msg = platform_strdup(msg);
266 }
267 return false;
268}
const char * asciichat_suggest_enum_value(const char *option_name, const char *input)

References asciichat_suggest_enum_value(), platform_strdup(), and safe_snprintf().

◆ parse_color_setting()

bool parse_color_setting ( const char *  arg,
void *  dest,
char **  error_msg 
)

Definition at line 161 of file parsers.c.

161 {
162 if (!dest) {
163 if (error_msg) {
164 *error_msg = platform_strdup("Internal error: NULL destination");
165 }
166 return false;
167 }
168
169 // Use generic parser with color setting lookup table
170 // Default to TRUE if no arg provided
171 if (!arg || arg[0] == '\0') {
172 int *color_setting = (int *)dest;
173 *color_setting = COLOR_SETTING_TRUE;
174 return true;
175 }
176
177 return parse_setting_generic(arg, dest, g_color_setting_map, error_msg);
178}

References platform_strdup().

Referenced by options_init().

◆ parse_log_file()

bool parse_log_file ( const char *  arg,
void *  dest,
char **  error_msg 
)

Definition at line 1007 of file parsers.c.

1007 {
1008 if (!arg || !dest) {
1009 if (error_msg) {
1010 *error_msg = platform_strdup("Internal error: NULL argument or destination");
1011 }
1012 return false;
1013 }
1014
1015 // Validate and normalize the log file path
1016 char *normalized = NULL;
1017 asciichat_error_t result = path_validate_user_path(arg, PATH_ROLE_LOG_FILE, &normalized);
1018
1019 if (result != ASCIICHAT_OK) {
1020 if (error_msg) {
1021 asciichat_error_context_t err_ctx;
1022 if (HAS_ERRNO(&err_ctx)) {
1023 *error_msg = platform_strdup(err_ctx.context_message);
1024 } else {
1025 *error_msg = platform_strdup("Log file path validation failed");
1026 }
1027 }
1028 return false;
1029 }
1030
1031 // Copy validated path to destination
1032 char *log_file_buf = (char *)dest;
1033 const size_t max_size = 256;
1034 SAFE_STRNCPY(log_file_buf, normalized, max_size - 1);
1035 log_file_buf[max_size - 1] = '\0';
1036
1037 SAFE_FREE(normalized);
1038 return true;
1039}
asciichat_error_t path_validate_user_path(const char *input, path_role_t role, char **normalized_out)
Definition path.c:974

References path_validate_user_path(), and platform_strdup().

◆ parse_log_level()

bool parse_log_level ( const char *  arg,
void *  dest,
char **  error_msg 
)

Definition at line 408 of file parsers.c.

408 {
409 if (!dest) {
410 if (error_msg) {
411 *error_msg = platform_strdup("Internal error: NULL destination");
412 }
413 return false;
414 }
415
416 log_level_t *log_level = (log_level_t *)dest;
417
418 // If no argument provided, use the default log level (based on build type)
419 if (!arg || arg[0] == '\0') {
420 *log_level = DEFAULT_LOG_LEVEL;
421 return true;
422 }
423
424 char lower[32];
425 to_lower(arg, lower, sizeof(lower));
426
427 // Development level
428 if (strcmp(lower, "dev") == 0 || strcmp(lower, "development") == 0 || strcmp(lower, "0") == 0) {
429 *log_level = LOG_DEV;
430 return true;
431 }
432
433 // Debug level
434 if (strcmp(lower, "debug") == 0 || strcmp(lower, "dbg") == 0 || strcmp(lower, "1") == 0) {
435 *log_level = LOG_DEBUG;
436 return true;
437 }
438
439 // Info level
440 if (strcmp(lower, "info") == 0 || strcmp(lower, "information") == 0 || strcmp(lower, "2") == 0) {
441 *log_level = LOG_INFO;
442 return true;
443 }
444
445 // Warning level
446 if (strcmp(lower, "warn") == 0 || strcmp(lower, "warning") == 0 || strcmp(lower, "3") == 0) {
447 *log_level = LOG_WARN;
448 return true;
449 }
450
451 // Error level
452 if (strcmp(lower, "error") == 0 || strcmp(lower, "err") == 0 || strcmp(lower, "4") == 0) {
453 *log_level = LOG_ERROR;
454 return true;
455 }
456
457 // Fatal level
458 if (strcmp(lower, "fatal") == 0 || strcmp(lower, "5") == 0) {
459 *log_level = LOG_FATAL;
460 return true;
461 }
462
463 // Invalid value
464 if (error_msg) {
465 char msg[256];
466 safe_snprintf(msg, sizeof(msg), "Invalid log level '%s'. Valid values: dev, debug, info, warn, error, fatal", arg);
467 *error_msg = platform_strdup(msg);
468 }
469 return false;
470}

References platform_strdup(), and safe_snprintf().

Referenced by options_init().

◆ parse_palette_chars()

bool parse_palette_chars ( const char *  arg,
void *  dest,
char **  error_msg 
)

Definition at line 809 of file parsers.c.

809 {
810 if (!arg || !dest) {
811 if (error_msg) {
812 *error_msg = platform_strdup("Internal error: NULL argument or destination");
813 }
814 return false;
815 }
816
817 // The dest pointer points to the palette_custom field in options_t
818 // We need to get the full options_t struct to call parse_palette_chars_option
819 // Since we only have the field pointer, we need to handle this directly
820
821 char *palette_custom = (char *)dest;
822
823 size_t len = strlen(arg);
824 if (len == 0) {
825 if (error_msg) {
826 *error_msg = platform_strdup("Invalid palette-chars: value cannot be empty");
827 }
828 return false;
829 }
830
831 if (len >= 256) {
832 if (error_msg) {
833 char msg[256];
834 safe_snprintf(msg, sizeof(msg), "Invalid palette-chars: too long (%zu chars, max 255)", len);
835 *error_msg = platform_strdup(msg);
836 }
837 return false;
838 }
839
840 // Copy the palette characters
841 SAFE_STRNCPY(palette_custom, arg, 256);
842 palette_custom[255] = '\0';
843
844 // Also set the palette type to custom by calculating back to options_t pointer
845 // dest points to options_t.palette_custom, so we can get options_t* using offset arithmetic
846 options_t *opts = (options_t *)((char *)dest - offsetof(options_t, palette_custom));
847 opts->palette_type = PALETTE_CUSTOM;
848
849 return true;
850}

References platform_strdup(), and safe_snprintf().

◆ parse_palette_type()

bool parse_palette_type ( const char *  arg,
void *  dest,
char **  error_msg 
)

Definition at line 345 of file parsers.c.

345 {
346 if (!arg || !dest) {
347 if (error_msg) {
348 *error_msg = platform_strdup("Internal error: NULL argument or destination");
349 }
350 return false;
351 }
352
353 palette_type_t *palette_type = (palette_type_t *)dest;
354 char lower[32];
355 to_lower(arg, lower, sizeof(lower));
356
357 // Standard palette
358 if (strcmp(lower, "standard") == 0 || strcmp(lower, "std") == 0 || strcmp(lower, "0") == 0) {
359 *palette_type = PALETTE_STANDARD;
360 return true;
361 }
362
363 // Blocks palette
364 if (strcmp(lower, "blocks") == 0 || strcmp(lower, "block") == 0 || strcmp(lower, "1") == 0) {
365 *palette_type = PALETTE_BLOCKS;
366 return true;
367 }
368
369 // Digital palette
370 if (strcmp(lower, "digital") == 0 || strcmp(lower, "dig") == 0 || strcmp(lower, "2") == 0) {
371 *palette_type = PALETTE_DIGITAL;
372 return true;
373 }
374
375 // Minimal palette
376 if (strcmp(lower, "minimal") == 0 || strcmp(lower, "min") == 0 || strcmp(lower, "3") == 0) {
377 *palette_type = PALETTE_MINIMAL;
378 return true;
379 }
380
381 // Cool palette
382 if (strcmp(lower, "cool") == 0 || strcmp(lower, "4") == 0) {
383 *palette_type = PALETTE_COOL;
384 return true;
385 }
386
387 // Custom palette
388 if (strcmp(lower, "custom") == 0 || strcmp(lower, "5") == 0) {
389 *palette_type = PALETTE_CUSTOM;
390 return true;
391 }
392
393 // Invalid value - suggest closest match
394 if (error_msg) {
395 char msg[256];
396 const char *suggestion = asciichat_suggest_enum_value("palette", arg);
397 if (suggestion) {
398 safe_snprintf(msg, sizeof(msg), "Invalid palette type '%s'. Did you mean '%s'?", arg, suggestion);
399 } else {
400 safe_snprintf(msg, sizeof(msg),
401 "Invalid palette type '%s'. Valid values: standard, blocks, digital, minimal, cool, custom", arg);
402 }
403 *error_msg = platform_strdup(msg);
404 }
405 return false;
406}

References asciichat_suggest_enum_value(), platform_strdup(), and safe_snprintf().

Referenced by set_palette().

◆ parse_port_option()

bool parse_port_option ( const char *  arg,
void *  dest,
char **  error_msg 
)

Definition at line 472 of file parsers.c.

472 {
473 if (!arg || !dest) {
474 if (error_msg) {
475 *error_msg = platform_strdup("Internal error: NULL argument or destination");
476 }
477 return false;
478 }
479
480 int *port_value = (int *)dest;
481 uint16_t port_num;
482
483 // Use the existing parse_port function for validation
484 asciichat_error_t err = parse_port(arg, &port_num);
485 if (err != ASCIICHAT_OK) {
486 if (error_msg) {
487 char msg[256];
488 safe_snprintf(msg, sizeof(msg), "Invalid port '%s'. Port must be a number between 1 and 65535.", arg);
489 *error_msg = platform_strdup(msg);
490 }
491 return false;
492 }
493
494 *port_value = (int)port_num;
495 return true;
496}
asciichat_error_t parse_port(const char *str, uint16_t *out_port)
Definition parsing.c:251

References parse_port(), platform_strdup(), and safe_snprintf().

◆ parse_render_mode()

bool parse_render_mode ( const char *  arg,
void *  dest,
char **  error_msg 
)

Definition at line 300 of file parsers.c.

300 {
301 if (!arg || !dest) {
302 if (error_msg) {
303 *error_msg = platform_strdup("Internal error: NULL argument or destination");
304 }
305 return false;
306 }
307
308 render_mode_t *render_mode = (render_mode_t *)dest;
309 char lower[32];
310 to_lower(arg, lower, sizeof(lower));
311
312 // Foreground mode
313 if (strcmp(lower, "foreground") == 0 || strcmp(lower, "fg") == 0 || strcmp(lower, "0") == 0) {
314 *render_mode = RENDER_MODE_FOREGROUND;
315 return true;
316 }
317
318 // Background mode
319 if (strcmp(lower, "background") == 0 || strcmp(lower, "bg") == 0 || strcmp(lower, "1") == 0) {
320 *render_mode = RENDER_MODE_BACKGROUND;
321 return true;
322 }
323
324 // Half-block mode
325 if (strcmp(lower, "half-block") == 0 || strcmp(lower, "hb") == 0 || strcmp(lower, "2") == 0) {
326 *render_mode = RENDER_MODE_HALF_BLOCK;
327 return true;
328 }
329
330 // Invalid value - suggest closest match
331 if (error_msg) {
332 char msg[256];
333 const char *suggestion = asciichat_suggest_enum_value("render-mode", arg);
334 if (suggestion) {
335 safe_snprintf(msg, sizeof(msg), "Invalid render mode '%s'. Did you mean '%s'?", arg, suggestion);
336 } else {
337 safe_snprintf(msg, sizeof(msg), "Invalid render mode '%s'. Valid values: foreground, background, half-block",
338 arg);
339 }
340 *error_msg = platform_strdup(msg);
341 }
342 return false;
343}

References asciichat_suggest_enum_value(), platform_strdup(), and safe_snprintf().

◆ parse_server_bind_address()

int parse_server_bind_address ( const char *  arg,
void *  config,
char **  remaining,
int  num_remaining,
char **  error_msg 
)

Parse server bind address positional argument.

Implements the server bind address parsing logic from server.c. Can consume 1 argument per call, handling IPv4 or IPv6 bind addresses. The positional arg system will call this multiple times for multiple args.

Definition at line 512 of file parsers.c.

512 {
513 if (!arg || !config) {
514 if (error_msg) {
515 *error_msg = platform_strdup("Internal error: NULL argument or config");
516 }
517 return -1;
518 }
519
520 // Assume config struct has address and address6 fields (OPTIONS_BUFF_SIZE each)
521 // This is a simplified version that assumes standard options_state layout
522 char *address = (char *)config + offsetof(struct options_state, address);
523 char *address6 = (char *)config + offsetof(struct options_state, address6);
524
525 int consumed = 0;
526
527 // Parse first argument (IPv4 or IPv6)
528 char parsed_addr[OPTIONS_BUFF_SIZE];
529 const char *addr_to_check = arg;
530 if (parse_ipv6_address(arg, parsed_addr, sizeof(parsed_addr)) == 0) {
531 addr_to_check = parsed_addr;
532 }
533
534 // Check if it's IPv4 or IPv6
535 if (is_valid_ipv4(addr_to_check)) {
536 // Check if we already have a non-default IPv4 address
537 // Allow overwriting defaults (localhost, 0.0.0.0)
538 if (address[0] != '\0' && !is_localhost_ipv4(address) && strcmp(address, "localhost") != 0 &&
539 strcmp(address, "0.0.0.0") != 0) {
540 if (error_msg) {
541 char msg[256];
542 safe_snprintf(msg, sizeof(msg),
543 "Cannot specify multiple IPv4 addresses.\n"
544 "Already have: %s\n"
545 "Cannot add: %s",
546 address, addr_to_check);
547 *error_msg = platform_strdup(msg);
548 }
549 return -1;
550 }
551 SAFE_SNPRINTF(address, OPTIONS_BUFF_SIZE, "%s", addr_to_check);
552 consumed = 1;
553 } else if (is_valid_ipv6(addr_to_check)) {
554 // Check if we already have a non-default IPv6 address
555 // Allow overwriting default (::1)
556 if (address6[0] != '\0' && !is_localhost_ipv6(address6)) {
557 if (error_msg) {
558 char msg[256];
559 safe_snprintf(msg, sizeof(msg),
560 "Cannot specify multiple IPv6 addresses.\n"
561 "Already have: %s\n"
562 "Cannot add: %s",
563 address6, addr_to_check);
564 *error_msg = platform_strdup(msg);
565 }
566 return -1;
567 }
568 SAFE_SNPRINTF(address6, OPTIONS_BUFF_SIZE, "%s", addr_to_check);
569 consumed = 1;
570 } else {
571 if (error_msg) {
572 char msg[512];
573 safe_snprintf(msg, sizeof(msg),
574 "Invalid IP address '%s'.\n"
575 "Server bind addresses must be valid IPv4 or IPv6 addresses.\n"
576 "Examples:\n"
577 " ascii-chat server 0.0.0.0\n"
578 " ascii-chat server ::1\n"
579 " ascii-chat server 0.0.0.0 ::1",
580 arg);
581 *error_msg = platform_strdup(msg);
582 }
583 return -1;
584 }
585
586 // Try to parse second address if available
587 if (remaining && num_remaining > 0 && remaining[0]) {
588 const char *second_arg = remaining[0];
589 memset(parsed_addr, 0, sizeof(parsed_addr));
590 addr_to_check = second_arg;
591 if (parse_ipv6_address(second_arg, parsed_addr, sizeof(parsed_addr)) == 0) {
592 addr_to_check = parsed_addr;
593 }
594
595 if (is_valid_ipv4(addr_to_check)) {
596 // Second is IPv4
597 if (address[0] != '\0' && !is_localhost_ipv4(address) && strcmp(address, "localhost") != 0 &&
598 strcmp(address, "0.0.0.0") != 0) {
599 // Already have an IPv4, can't add another
600 return consumed;
601 }
602 if (is_valid_ipv4(arg)) {
603 // First was also IPv4, can't have two IPv4s
604 return consumed;
605 }
606 // First was IPv6, second is IPv4 - accept both
607 SAFE_SNPRINTF(address, OPTIONS_BUFF_SIZE, "%s", addr_to_check);
608 consumed = 2;
609 } else if (is_valid_ipv6(addr_to_check)) {
610 // Second is IPv6
611 if (address6[0] != '\0' && !is_localhost_ipv6(address6)) {
612 // Already have an IPv6, can't add another
613 return consumed;
614 }
615 if (is_valid_ipv6(arg)) {
616 // First was also IPv6, can't have two IPv6s
617 return consumed;
618 }
619 // First was IPv4, second is IPv6 - accept both
620 SAFE_SNPRINTF(address6, OPTIONS_BUFF_SIZE, "%s", addr_to_check);
621 consumed = 2;
622 }
623 }
624
625 return consumed;
626}
int is_localhost_ipv6(const char *ip)
Definition ip.c:1320
int is_localhost_ipv4(const char *ip)
Definition ip.c:1299
int parse_ipv6_address(const char *input, char *output, size_t output_size)
Definition ip.c:158

References is_localhost_ipv4(), is_localhost_ipv6(), is_valid_ipv4(), is_valid_ipv6(), parse_ipv6_address(), platform_strdup(), and safe_snprintf().

Referenced by options_preset_unified().

◆ parse_timestamp()

bool parse_timestamp ( const char *  arg,
void *  dest,
char **  error_msg 
)

Custom parser for –seek flag.

Accepts both "hh:mm:ss.ms" format and plain seconds format. Examples:

  • "30" = 30 seconds
  • "30.5" = 30.5 seconds
  • "1:30" = 1 minute 30 seconds (90 seconds)
  • "1:30.5" = 1 minute 30.5 seconds (90.5 seconds)
  • "0:1:30.5" = 1 minute 30.5 seconds (90.5 seconds)
  • "1:2:30.5" = 1 hour 2 minutes 30.5 seconds (3750.5 seconds)

Definition at line 890 of file parsers.c.

890 {
891 if (!arg || arg[0] == '\0') {
892 if (error_msg) {
893 *error_msg = platform_strdup("--seek requires a timestamp argument");
894 }
895 return false;
896 }
897
898 double *timestamp = (double *)dest;
899 char *endptr;
900 long strtol_result;
901
902 // Count colons to determine format
903 int colon_count = 0;
904 for (const char *p = arg; *p; p++) {
905 if (*p == ':')
906 colon_count++;
907 }
908
909 if (colon_count == 0) {
910 // Plain seconds format: "30" or "30.5"
911 *timestamp = strtod(arg, &endptr);
912 if (*endptr != '\0' || *timestamp < 0.0) {
913 if (error_msg) {
914 *error_msg = platform_strdup("Invalid timestamp: expected non-negative seconds");
915 }
916 return false;
917 }
918 return true;
919 } else if (colon_count == 1) {
920 // MM:SS or MM:SS.ms format
921 strtol_result = strtol(arg, &endptr, 10);
922 if (*endptr != ':' || strtol_result < 0) {
923 if (error_msg) {
924 *error_msg = platform_strdup("Invalid timestamp: expected MM:SS or MM:SS.ms format");
925 }
926 return false;
927 }
928 long minutes = strtol_result;
929 double seconds = strtod(endptr + 1, &endptr);
930 if (*endptr != '\0' && *endptr != '.' && *endptr != '\0') {
931 if (error_msg) {
932 *error_msg = platform_strdup("Invalid timestamp: expected MM:SS or MM:SS.ms format");
933 }
934 return false;
935 }
936 *timestamp = minutes * 60.0 + seconds;
937 return true;
938 } else if (colon_count == 2) {
939 // HH:MM:SS or HH:MM:SS.ms format
940 strtol_result = strtol(arg, &endptr, 10);
941 if (*endptr != ':' || strtol_result < 0) {
942 if (error_msg) {
943 *error_msg = platform_strdup("Invalid timestamp: expected HH:MM:SS or HH:MM:SS.ms format");
944 }
945 return false;
946 }
947 long hours = strtol_result;
948
949 strtol_result = strtol(endptr + 1, &endptr, 10);
950 if (*endptr != ':' || strtol_result < 0 || strtol_result >= 60) {
951 if (error_msg) {
952 *error_msg = platform_strdup("Invalid timestamp: minutes must be 0-59");
953 }
954 return false;
955 }
956 long minutes = strtol_result;
957
958 double seconds = strtod(endptr + 1, &endptr);
959 if (*endptr != '\0') {
960 if (error_msg) {
961 *error_msg = platform_strdup("Invalid timestamp: expected HH:MM:SS or HH:MM:SS.ms format");
962 }
963 return false;
964 }
965 *timestamp = hours * (double)SEC_PER_HOUR + minutes * (double)SEC_PER_MIN + seconds;
966 return true;
967 } else {
968 if (error_msg) {
969 *error_msg = platform_strdup("Invalid timestamp format: too many colons");
970 }
971 return false;
972 }
973}

References platform_strdup().

◆ parse_utf8_setting()

bool parse_utf8_setting ( const char *  arg,
void *  dest,
char **  error_msg 
)

Definition at line 192 of file parsers.c.

192 {
193 if (!dest) {
194 if (error_msg) {
195 *error_msg = platform_strdup("Internal error: NULL destination");
196 }
197 return false;
198 }
199
200 // Use generic parser with UTF-8 setting lookup table
201 // Default to TRUE if no arg provided
202 if (!arg || arg[0] == '\0') {
203 int *utf8_setting = (int *)dest;
204 *utf8_setting = UTF8_SETTING_TRUE;
205 return true;
206 }
207
208 return parse_setting_generic(arg, dest, g_utf8_setting_map, error_msg);
209}

References platform_strdup().

◆ parse_verbose_flag()

bool parse_verbose_flag ( const char *  arg,
void *  dest,
char **  error_msg 
)

Definition at line 852 of file parsers.c.

852 {
853 (void)error_msg; // Unused but required by function signature
854
855 // If arg is NULL or starts with a flag, just increment
856 // Otherwise try to parse as integer count
857 unsigned short int *verbose_level = (unsigned short int *)dest;
858
859 if (!arg || arg[0] == '\0') {
860 // No argument provided, just increment
861 (*verbose_level)++;
862 return true;
863 }
864
865 // Try to parse as integer count
866 char *endptr;
867 long value = strtol(arg, &endptr, 10);
868 if (*endptr == '\0' && value >= 0 && value <= 100) {
869 *verbose_level = (unsigned short int)value;
870 return true;
871 }
872
873 // If it didn't parse as int, treat as flag increment
874 (*verbose_level)++;
875 return true;
876}

◆ parse_volume()

bool parse_volume ( const char *  arg,
void *  dest,
char **  error_msg 
)

Definition at line 975 of file parsers.c.

975 {
976 if (!arg || !dest) {
977 if (error_msg) {
978 *error_msg = platform_strdup("Internal error: NULL argument or destination");
979 }
980 return false;
981 }
982
983 float *volume = (float *)dest;
984 char *endptr;
985 float val = strtof(arg, &endptr);
986
987 if (*endptr != '\0' || arg == endptr) {
988 if (error_msg) {
989 *error_msg = platform_strdup("Invalid volume value. Must be a number between 0.0 and 1.0");
990 }
991 return false;
992 }
993
994 if (val < 0.0f || val > 1.0f) {
995 if (error_msg) {
996 char buf[256];
997 SAFE_SNPRINTF(buf, sizeof(buf), "Volume must be between 0.0 and 1.0 (got %.2f)", val);
998 *error_msg = platform_strdup(buf);
999 }
1000 return false;
1001 }
1002
1003 *volume = val;
1004 return true;
1005}

References platform_strdup().