@@ -37,7 +37,7 @@ int even_validator(argus_t *argus, void *option_ptr, validator_data_t data)
3737
3838// Helper macro
3939#define V_EVEN() \
40- MAKE_VALIDATOR(even_validator, _ V_DATA_CUSTOM_ (NULL), ORDER_POST)
40+ MAKE_VALIDATOR(even_validator, NULL, _ V_DATA_CUSTOM_ (NULL), ORDER_POST)
4141
4242// Usage
4343OPTION_INT('n', "number", HELP("Even number"), VALIDATOR(V_EVEN()))
@@ -72,7 +72,7 @@ int email_format_validator(argus_t *argus, void *value_ptr, validator_data_t dat
7272}
7373
7474#define V_EMAIL_FORMAT() \
75- MAKE_VALIDATOR(email_format_validator, _V_DATA_CUSTOM_(NULL), ORDER_PRE)
75+ MAKE_VALIDATOR(email_format_validator, NULL, _V_DATA_CUSTOM_(NULL), ORDER_PRE)
7676
7777// Usage
7878OPTION_STRING('e', "email", HELP("Email address"), VALIDATOR(V_EMAIL_FORMAT()))
@@ -103,7 +103,7 @@ int divisible_validator(argus_t *argus, void *option_ptr, validator_data_t data)
103103}
104104
105105#define V_DIVISIBLE_BY(n) \
106- MAKE_VALIDATOR(divisible_validator, _ V_DATA_CUSTOM_ (n), ORDER_POST)
106+ MAKE_VALIDATOR(divisible_validator, NULL, _ V_DATA_CUSTOM_ (n), ORDER_POST)
107107
108108// Usage
109109OPTION_INT('p', "port", HELP("Port (multiple of 100)"),
@@ -141,7 +141,7 @@ int advanced_range_validator(argus_t *argus, void *option_ptr, validator_data_t
141141}
142142
143143#define V_ADVANCED_RANGE(min, max, allow_zero) \
144- MAKE_VALIDATOR(advanced_range_validator, \
144+ MAKE_VALIDATOR(advanced_range_validator, NULL, \
145145 _V_DATA_CUSTOM_(&((range_config_t){min, max, allow_zero})), \
146146 ORDER_POST)
147147
@@ -176,7 +176,7 @@ int max_greater_than_min_validator(argus_t *argus, void *option_ptr, validator_d
176176}
177177
178178#define V_GREATER_THAN(option_name) \
179- MAKE_VALIDATOR(max_greater_than_min_validator, _ V_DATA_CUSTOM_ (option_name), ORDER_POST)
179+ MAKE_VALIDATOR(max_greater_than_min_validator, NULL, _ V_DATA_CUSTOM_ (option_name), ORDER_POST)
180180
181181ARGUS_OPTIONS(
182182 options,
@@ -214,7 +214,7 @@ int company_email_validator(argus_t *argus, void *option_ptr, validator_data_t d
214214}
215215
216216#define V_COMPANY_EMAIL(domain) \
217- MAKE_VALIDATOR(company_email_validator, _ V_DATA_CUSTOM_ (domain), ORDER_POST)
217+ MAKE_VALIDATOR(company_email_validator, NULL, _ V_DATA_CUSTOM_ (domain), ORDER_POST)
218218
219219// Usage
220220OPTION_STRING('e', "email", HELP("Company email"),
@@ -263,11 +263,11 @@ int case_validator(argus_t *argus, void *value_ptr, validator_data_t data)
263263}
264264
265265#define V_LOWERCASE() \
266- MAKE_VALIDATOR(case_validator, _V_DATA_CUSTOM_(&(case_requirement_t){LOWERCASE}), ORDER_PRE)
266+ MAKE_VALIDATOR(case_validator, NULL, _V_DATA_CUSTOM_(&(case_requirement_t){LOWERCASE}), ORDER_PRE)
267267#define V_UPPERCASE() \
268- MAKE_VALIDATOR(case_validator, _V_DATA_CUSTOM_(&(case_requirement_t){UPPERCASE}), ORDER_PRE)
268+ MAKE_VALIDATOR(case_validator, NULL, _V_DATA_CUSTOM_(&(case_requirement_t){UPPERCASE}), ORDER_PRE)
269269#define V_MIXED_CASE() \
270- MAKE_VALIDATOR(case_validator, _V_DATA_CUSTOM_(&(case_requirement_t){MIXED}), ORDER_PRE)
270+ MAKE_VALIDATOR(case_validator, NULL, _V_DATA_CUSTOM_(&(case_requirement_t){MIXED}), ORDER_PRE)
271271```
272272
273273</TabItem >
@@ -299,9 +299,9 @@ int file_exists_validator(argus_t *argus, void *option_ptr, validator_data_t dat
299299}
300300
301301#define V_FILE_EXISTS() \
302- MAKE_VALIDATOR(file_exists_validator, _ V_DATA_CUSTOM_ (true), ORDER_POST)
302+ MAKE_VALIDATOR(file_exists_validator, NULL, _ V_DATA_CUSTOM_ (true), ORDER_POST)
303303#define V_FILE_NOT_EXISTS() \
304- MAKE_VALIDATOR(file_exists_validator, _ V_DATA_CUSTOM_ (false), ORDER_POST)
304+ MAKE_VALIDATOR(file_exists_validator, NULL, _ V_DATA_CUSTOM_ (false), ORDER_POST)
305305
306306// Usage
307307OPTION_STRING('i', "input", HELP("Input file"), VALIDATOR(V_FILE_EXISTS()))
@@ -335,13 +335,190 @@ int array_all_positive_validator(argus_t *argus, void *option_ptr, validator_dat
335335}
336336
337337#define V_ALL_POSITIVE() \
338- MAKE_VALIDATOR(array_all_positive_validator, _V_DATA_CUSTOM_(NULL), ORDER_POST)
338+ MAKE_VALIDATOR(array_all_positive_validator, NULL, _V_DATA_CUSTOM_(NULL), ORDER_POST)
339339
340340// Usage
341341OPTION_ARRAY_INT('p', "ports", HELP("Port numbers"),
342342 VALIDATOR(V_COUNT(1, 10), V_ALL_POSITIVE()))
343343```
344344
345+ ## Custom Formatters
346+
347+ Add custom formatting to display validator constraints in help output:
348+
349+ <Tabs >
350+ <TabItem value =" basic-formatter " label =" Basic Formatter " default >
351+
352+ ``` c
353+ // Divisibility validator with formatter
354+ int divisible_validator (argus_t * argus, void * option_ptr, validator_data_t data)
355+ {
356+ argus_option_t * option = (argus_option_t * )option_ptr;
357+ int divisor = (int)data.custom;
358+
359+ if (option->value.as_int % divisor != 0) {
360+ ARGUS_PARSING_ERROR(argus, "Value must be divisible by %d", divisor);
361+ return ARGUS_ERROR_INVALID_VALUE;
362+ }
363+ return ARGUS_SUCCESS;
364+ }
365+
366+ // Formatter function
367+ char * format_divisible_validator(validator_data_t data)
368+ {
369+ char * result = malloc(64);
370+ if (!result)
371+ return NULL;
372+
373+ snprintf(result, 64, "divisible by %d", (int)data.custom);
374+ return result;
375+ }
376+
377+ #define V_DIVISIBLE_BY(n) \
378+ MAKE_VALIDATOR(divisible_validator, format_divisible_validator, _ V_DATA_CUSTOM_ (n), ORDER_POST)
379+
380+ // Usage
381+ OPTION_INT('p', "port", HELP("Port number"), VALIDATOR(V_DIVISIBLE_BY(100)))
382+ ```
383+
384+ **Generated help:**
385+ ```bash
386+ -p, --port <NUM> - Port number (divisible by 100)
387+ ```
388+
389+ </TabItem >
390+ <TabItem value =" simple-formatter " label =" Simple Range " >
391+
392+ ``` c
393+ // Simple range validator for percentages
394+ char *format_percentage_validator (validator_data_t data)
395+ {
396+ char * result = malloc(16);
397+ if (!result)
398+ return NULL;
399+
400+ snprintf(result, 16, "0-100");
401+ return result;
402+ }
403+
404+ #define V_PERCENTAGE() \
405+ MAKE_VALIDATOR(percentage_validator, format_percentage_validator, \
406+ _ V_DATA_CUSTOM_ (NULL), ORDER_POST)
407+
408+ // Usage
409+ OPTION_INT('q', "quality", HELP("Compression quality"),
410+ VALIDATOR(V_PERCENTAGE()))
411+ ```
412+
413+ **Generated help:**
414+ ```bash
415+ -q, --quality <0-100> - Compression quality
416+ ```
417+
418+ </TabItem >
419+ <TabItem value =" domain-formatter " label =" Domain Validator " >
420+
421+ ``` c
422+ // Company email validator with formatter
423+ char *format_domain_validator (validator_data_t data)
424+ {
425+ const char * domain = (const char * )data.custom;
426+ char * result = malloc(128);
427+ if (!result)
428+ return NULL;
429+
430+ snprintf(result, 128, "company domain %s", domain);
431+ return result;
432+ }
433+
434+ #define V_DOMAIN_EMAIL(domain) \
435+ MAKE_VALIDATOR(company_email_validator, format_domain_validator, \
436+ _ V_DATA_CUSTOM_ (domain), ORDER_POST)
437+
438+ // Usage
439+ OPTION_STRING('e', "email", HELP("Company email"),
440+ VALIDATOR(V_DOMAIN_EMAIL("example.com")))
441+ ```
442+
443+ **Generated help:**
444+ ```bash
445+ -e, --email <STR> - Company email (company domain example.com)
446+ ```
447+
448+ </TabItem >
449+ <TabItem value =" ip-formatter " label =" IP Validator " >
450+
451+ ``` c
452+ typedef struct {
453+ bool allow_ipv6;
454+ bool allow_private;
455+ } ip_config_t ;
456+
457+ char *format_ip_validator (validator_data_t data)
458+ {
459+ ip_config_t * config = (ip_config_t * )data.custom;
460+ char * result = malloc(128);
461+ if (!result)
462+ return NULL;
463+
464+ if (config->allow_ipv6 && config->allow_private) {
465+ safe_strcpy(result, 128, "IPv4 or IPv6 address");
466+ } else if (config->allow_ipv6) {
467+ safe_strcpy(result, 128, "IPv4 or IPv6, no private addresses");
468+ } else if (config->allow_private) {
469+ safe_strcpy(result, 128, "IPv4 address");
470+ } else {
471+ safe_strcpy(result, 128, "IPv4, no private addresses");
472+ }
473+
474+ return result;
475+ }
476+
477+ #define V_IP_ADDRESS(ipv6, private) \
478+ MAKE_VALIDATOR(ip_validator, format_ip_validator, \
479+ _ V_DATA_CUSTOM_ (&((ip_config_t){ipv6, private})), \
480+ ORDER_POST)
481+
482+ // Usage
483+ OPTION_STRING('i', "ip", HELP("Server IP"),
484+ VALIDATOR(V_IP_ADDRESS(false, false)))
485+ ```
486+
487+ **Generated help:**
488+ ```bash
489+ -i, --ip <STR> - Server IP (IPv4, no private addresses)
490+ ```
491+
492+ </TabItem >
493+ </Tabs >
494+
495+ ### Formatter Implementation
496+
497+ ``` c
498+ // Template for formatter functions
499+ char *format_my_validator (validator_data_t data)
500+ {
501+ // Allocate memory for result
502+ char * result = malloc(BUFFER_SIZE);
503+ if (!result)
504+ return NULL;
505+
506+ // Format based on your data
507+ snprintf(result, BUFFER_SIZE, "your descriptive format");
508+
509+ return result; // Memory will be freed by caller
510+ }
511+ ```
512+
513+ **Integration with `MAKE_VALIDATOR`:**
514+ ```c
515+ #define MY_VALIDATOR(param) \
516+ MAKE_VALIDATOR(my_validator_func, format_my_validator, \
517+ _V_DATA_CUSTOM_(param), ORDER_POST)
518+ ```
519+
520+ The formatter automatically integrates with the help system and appears in option descriptions or hints depending on the output format.
521+
345522## Combining Validators
346523
347524Multiple validators execute in order:
@@ -382,7 +559,7 @@ int descriptive_validator(argus_t *argus, void *option_ptr, validator_data_t dat
382559
383560// ✅ Reusable with parameters
384561#define V_MIN_WORDS(count) \
385- MAKE_VALIDATOR(min_words_validator, _ V_DATA_CUSTOM_ (count), ORDER_POST)
562+ MAKE_VALIDATOR(min_words_validator, NULL, _ V_DATA_CUSTOM_ (count), ORDER_POST)
386563
387564// ❌ Avoid these patterns
388565int bad_validator(argus_t * argus, void * option_ptr, validator_data_t data)
0 commit comments