├── .circleci ├── Dockerfile.test └── config.yml ├── .dockerignore ├── .editorconfig ├── .gitignore ├── CMakeLists.txt ├── README.md ├── snprintf.c ├── test ├── .gitignore ├── autohelp-1.out ├── autohelp.c ├── macro-1.out ├── macro.c ├── nocondense-1.out ├── nocondense-sloppy-1.out ├── nocondense-sloppy-2.out ├── nocondense-sloppy-3.out ├── nocondense-sloppy.c ├── nocondense.c ├── optional-longarg-1.out ├── optional-longarg.c ├── required-1.out ├── required.c ├── simple-1.out ├── simple.c ├── sloppyshorts-1.out ├── sloppyshorts.c └── test-case.sh ├── xopt.c └── xopt.h /.circleci/Dockerfile.test: -------------------------------------------------------------------------------- 1 | FROM alpine:3.7 AS base 2 | WORKDIR /src 3 | RUN apk --update add alpine-sdk cmake bash 4 | COPY . ./ 5 | WORKDIR /src/build 6 | 7 | # Debug 8 | FROM base 9 | RUN cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=On 10 | RUN cmake --build . 11 | RUN ctest -VV 12 | 13 | # Release 14 | FROM base 15 | RUN cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=On 16 | RUN cmake --build . 17 | RUN ctest -VV 18 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | machine: true 5 | steps: 6 | - checkout 7 | - run: docker build -f .circleci/Dockerfile.test . 8 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.coffee] 11 | indent_style = space 12 | 13 | [{package.json,*.yml}] 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw[a-p] 2 | /bin/ 3 | /build/ 4 | /.tup/ 5 | /*.o 6 | /*.a 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | project (xopt) 3 | 4 | add_library (xopt STATIC "${CMAKE_CURRENT_SOURCE_DIR}/xopt.c") 5 | 6 | if (APPLE OR UNIX) 7 | set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror -pedantic -std=c11 -D_XOPEN_SOURCE=600 -fdiagnostics-color=always -fvisibility=hidden") 8 | set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g3 -O0") 9 | set (CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Ofast") 10 | endif () 11 | 12 | function (_xopt_test name) 13 | if (NOT TARGET "xopt-test-${name}") 14 | add_executable ("xopt-test-${name}" "${CMAKE_CURRENT_SOURCE_DIR}/test/${name}.c") 15 | target_link_libraries ("xopt-test-${name}" xopt) 16 | endif () 17 | 18 | set (testname "xopt-${name}-test") 19 | set (testnum 1) 20 | 21 | while (TEST "${testname}-${testnum}") 22 | math (EXPR testnum "${testnum}+1") 23 | endwhile () 24 | 25 | add_test ( 26 | NAME "${testname}-${testnum}" 27 | COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/test/test-case.sh" $ "${CMAKE_CURRENT_SOURCE_DIR}/test/${name}-${testnum}.out" ${ARGN} 28 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/test") 29 | endfunction () 30 | 31 | if (BUILD_TESTING) 32 | enable_testing () 33 | _xopt_test (simple --some-int=10 --some-double=14.5 foo bar -- --some-other=20) 34 | _xopt_test (macro --some-int=10 --some-double=14.5 foo bar -- --some-other=20) 35 | _xopt_test (required --some-int=10 --some-double=14.5 --some-required=1337 foo bar -- --some-other=20) 36 | _xopt_test (optional-longarg -i 10 -d 14.5 foo bar -- --some-other=20) 37 | _xopt_test (autohelp --help -- --is-not-passed ignoreme) 38 | _xopt_test (sloppyshorts -i10 -d 14.5 "-ssome string" -m -mm -mmm foo bar -- --is-not-passed ignoreme) 39 | _xopt_test (nocondense-sloppy -i 10 -d 14.5 -s "some string" -m -mm -mmm foo bar -- --is-not-passed ignoreme) 40 | _xopt_test (nocondense-sloppy -i 10 -d 14.5 "-ssome string" -m -mm -mmm foo bar -- --is-not-passed ignoreme) 41 | _xopt_test (nocondense-sloppy -i 10 -d 14.5 "-ssome string" -m -m -m -m -m -m foo bar -- --is-not-passed ignoreme) 42 | _xopt_test (nocondense -i 10 -d 14.5 "-ssome string" -m -mm -mmm foo bar -- --is-not-passed ignoreme) 43 | endif () 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XOpt [![CircleCI](https://circleci.com/gh/Qix-/xopt.svg?style=svg)](https://circleci.com/gh/Qix-/xopt) 2 | The sane answer to POpt. 3 | 4 | XOpt is a command line argument parsing library written in ANSI C. XOpt 5 | accepts arguments in GNU format and focuses on clean definition, taking stress 6 | off the implementation. 7 | 8 | # License 9 | Originally by Josh Junon, released under [CC0](https://creativecommons.org/publicdomain/zero/1.0/). Go nuts. 10 | -------------------------------------------------------------------------------- /snprintf.c: -------------------------------------------------------------------------------- 1 | /* $Id: snprintf.c,v 1.9 2008/01/20 14:02:00 holger Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 1995 Patrick Powell. 5 | * 6 | * This code is based on code written by Patrick Powell . 7 | * It may be used for any purpose as long as this notice remains intact on all 8 | * source code distributions. 9 | */ 10 | 11 | /* 12 | * Copyright (c) 2008 Holger Weiss. 13 | * 14 | * This version of the code is maintained by Holger Weiss . 15 | * My changes to the code may freely be used, modified and/or redistributed for 16 | * any purpose. It would be nice if additions and fixes to this file (including 17 | * trivial code cleanups) would be sent back in order to let me include them in 18 | * the version available at . 19 | * However, this is not a requirement for using or redistributing (possibly 20 | * modified) versions of this file, nor is leaving this notice intact mandatory. 21 | */ 22 | 23 | /* 24 | * History 25 | * 26 | * 2008-01-20 Holger Weiss for C99-snprintf 1.1: 27 | * 28 | * Fixed the detection of infinite floating point values on IRIX (and 29 | * possibly other systems) and applied another few minor cleanups. 30 | * 31 | * 2008-01-06 Holger Weiss for C99-snprintf 1.0: 32 | * 33 | * Added a lot of new features, fixed many bugs, and incorporated various 34 | * improvements done by Andrew Tridgell , Russ Allbery 35 | * , Hrvoje Niksic , Damien Miller 36 | * , and others for the Samba, INN, Wget, and OpenSSH 37 | * projects. The additions include: support the "e", "E", "g", "G", and 38 | * "F" conversion specifiers (and use conversion style "f" or "F" for the 39 | * still unsupported "a" and "A" specifiers); support the "hh", "ll", "j", 40 | * "t", and "z" length modifiers; support the "#" flag and the (non-C99) 41 | * "'" flag; use localeconv(3) (if available) to get both the current 42 | * locale's decimal point character and the separator between groups of 43 | * digits; fix the handling of various corner cases of field width and 44 | * precision specifications; fix various floating point conversion bugs; 45 | * handle infinite and NaN floating point values; don't attempt to write to 46 | * the output buffer (which may be NULL) if a size of zero was specified; 47 | * check for integer overflow of the field width, precision, and return 48 | * values and during the floating point conversion; use the OUTCHAR() macro 49 | * instead of a function for better performance; provide asprintf(3) and 50 | * vasprintf(3) functions; add new test cases. The replacement functions 51 | * have been renamed to use an "rpl_" prefix, the function calls in the 52 | * main project (and in this file) must be redefined accordingly for each 53 | * replacement function which is needed (by using Autoconf or other means). 54 | * Various other minor improvements have been applied and the coding style 55 | * was cleaned up for consistency. 56 | * 57 | * 2007-07-23 Holger Weiss for Mutt 1.5.13: 58 | * 59 | * C99 compliant snprintf(3) and vsnprintf(3) functions return the number 60 | * of characters that would have been written to a sufficiently sized 61 | * buffer (excluding the '\0'). The original code simply returned the 62 | * length of the resulting output string, so that's been fixed. 63 | * 64 | * 1998-03-05 Michael Elkins for Mutt 0.90.8: 65 | * 66 | * The original code assumed that both snprintf(3) and vsnprintf(3) were 67 | * missing. Some systems only have snprintf(3) but not vsnprintf(3), so 68 | * the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF. 69 | * 70 | * 1998-01-27 Thomas Roessler for Mutt 0.89i: 71 | * 72 | * The PGP code was using unsigned hexadecimal formats. Unfortunately, 73 | * unsigned formats simply didn't work. 74 | * 75 | * 1997-10-22 Brandon Long for Mutt 0.87.1: 76 | * 77 | * Ok, added some minimal floating point support, which means this probably 78 | * requires libm on most operating systems. Don't yet support the exponent 79 | * (e,E) and sigfig (g,G). Also, fmtint() was pretty badly broken, it just 80 | * wasn't being exercised in ways which showed it, so that's been fixed. 81 | * Also, formatted the code to Mutt conventions, and removed dead code left 82 | * over from the original. Also, there is now a builtin-test, run with: 83 | * gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm && ./snprintf 84 | * 85 | * 2996-09-15 Brandon Long for Mutt 0.43: 86 | * 87 | * This was ugly. It is still ugly. I opted out of floating point 88 | * numbers, but the formatter understands just about everything from the 89 | * normal C string format, at least as far as I can tell from the Solaris 90 | * 2.5 printf(3S) man page. 91 | */ 92 | 93 | /* 94 | * ToDo 95 | * 96 | * - Add wide character support. 97 | * - Add support for "%a" and "%A" conversions. 98 | * - Create test routines which predefine the expected results. Our test cases 99 | * usually expose bugs in system implementations rather than in ours :-) 100 | */ 101 | 102 | /* 103 | * Usage 104 | * 105 | * 1) The following preprocessor macros should be defined to 1 if the feature or 106 | * file in question is available on the target system (by using Autoconf or 107 | * other means), though basic functionality should be available as long as 108 | * HAVE_STDARG_H and HAVE_STDLIB_H are defined correctly: 109 | * 110 | * HAVE_VSNPRINTF 111 | * HAVE_SNPRINTF 112 | * HAVE_VASPRINTF 113 | * HAVE_ASPRINTF 114 | * HAVE_STDARG_H 115 | * HAVE_STDDEF_H 116 | * HAVE_STDINT_H 117 | * HAVE_STDLIB_H 118 | * HAVE_INTTYPES_H 119 | * HAVE_LOCALE_H 120 | * HAVE_LOCALECONV 121 | * HAVE_LCONV_DECIMAL_POINT 122 | * HAVE_LCONV_THOUSANDS_SEP 123 | * HAVE_LONG_DOUBLE 124 | * HAVE_LONG_LONG_INT 125 | * HAVE_UNSIGNED_LONG_LONG_INT 126 | * HAVE_INTMAX_T 127 | * HAVE_UINTMAX_T 128 | * HAVE_UINTPTR_T 129 | * HAVE_PTRDIFF_T 130 | * HAVE_VA_COPY 131 | * HAVE___VA_COPY 132 | * 133 | * 2) The calls to the functions which should be replaced must be redefined 134 | * throughout the project files (by using Autoconf or other means): 135 | * 136 | * #define vsnprintf rpl_vsnprintf 137 | * #define snprintf rpl_snprintf 138 | * #define vasprintf rpl_vasprintf 139 | * #define asprintf rpl_asprintf 140 | * 141 | * 3) The required replacement functions should be declared in some header file 142 | * included throughout the project files: 143 | * 144 | * #if HAVE_CONFIG_H 145 | * #include 146 | * #endif 147 | * #if HAVE_STDARG_H 148 | * #include 149 | * #if !HAVE_VSNPRINTF 150 | * int rpl_vsnprintf(char *, size_t, const char *, va_list); 151 | * #endif 152 | * #if !HAVE_SNPRINTF 153 | * int rpl_snprintf(char *, size_t, const char *, ...); 154 | * #endif 155 | * #if !HAVE_VASPRINTF 156 | * int rpl_vasprintf(char **, const char *, va_list); 157 | * #endif 158 | * #if !HAVE_ASPRINTF 159 | * int rpl_asprintf(char **, const char *, ...); 160 | * #endif 161 | * #endif 162 | * 163 | * Autoconf macros for handling step 1 and step 2 are available at 164 | * . 165 | */ 166 | 167 | #if HAVE_CONFIG_H 168 | #include 169 | #endif /* HAVE_CONFIG_H */ 170 | 171 | #if TEST_SNPRINTF 172 | #include /* For pow(3), NAN, and INFINITY. */ 173 | #include /* For strcmp(3). */ 174 | #if defined(__NetBSD__) || \ 175 | defined(__FreeBSD__) || \ 176 | defined(__OpenBSD__) || \ 177 | defined(__NeXT__) || \ 178 | defined(__bsd__) 179 | #define OS_BSD 1 180 | #elif defined(sgi) || defined(__sgi) 181 | #ifndef __c99 182 | #define __c99 /* Force C99 mode to get included on IRIX 6.5.30. */ 183 | #endif /* !defined(__c99) */ 184 | #define OS_IRIX 1 185 | #define OS_SYSV 1 186 | #elif defined(__svr4__) 187 | #define OS_SYSV 1 188 | #elif defined(__linux__) 189 | #define OS_LINUX 1 190 | #endif /* defined(__NetBSD__) || defined(__FreeBSD__) || [...] */ 191 | #if HAVE_CONFIG_H /* Undefine definitions possibly done in config.h. */ 192 | #ifdef HAVE_SNPRINTF 193 | #undef HAVE_SNPRINTF 194 | #endif /* defined(HAVE_SNPRINTF) */ 195 | #ifdef HAVE_VSNPRINTF 196 | #undef HAVE_VSNPRINTF 197 | #endif /* defined(HAVE_VSNPRINTF) */ 198 | #ifdef HAVE_ASPRINTF 199 | #undef HAVE_ASPRINTF 200 | #endif /* defined(HAVE_ASPRINTF) */ 201 | #ifdef HAVE_VASPRINTF 202 | #undef HAVE_VASPRINTF 203 | #endif /* defined(HAVE_VASPRINTF) */ 204 | #ifdef snprintf 205 | #undef snprintf 206 | #endif /* defined(snprintf) */ 207 | #ifdef vsnprintf 208 | #undef vsnprintf 209 | #endif /* defined(vsnprintf) */ 210 | #ifdef asprintf 211 | #undef asprintf 212 | #endif /* defined(asprintf) */ 213 | #ifdef vasprintf 214 | #undef vasprintf 215 | #endif /* defined(vasprintf) */ 216 | #else /* By default, we assume a modern system for testing. */ 217 | #ifndef HAVE_STDARG_H 218 | #define HAVE_STDARG_H 1 219 | #endif /* HAVE_STDARG_H */ 220 | #ifndef HAVE_STDDEF_H 221 | #define HAVE_STDDEF_H 1 222 | #endif /* HAVE_STDDEF_H */ 223 | #ifndef HAVE_STDINT_H 224 | #define HAVE_STDINT_H 1 225 | #endif /* HAVE_STDINT_H */ 226 | #ifndef HAVE_STDLIB_H 227 | #define HAVE_STDLIB_H 1 228 | #endif /* HAVE_STDLIB_H */ 229 | #ifndef HAVE_INTTYPES_H 230 | #define HAVE_INTTYPES_H 1 231 | #endif /* HAVE_INTTYPES_H */ 232 | #ifndef HAVE_LOCALE_H 233 | #define HAVE_LOCALE_H 1 234 | #endif /* HAVE_LOCALE_H */ 235 | #ifndef HAVE_LOCALECONV 236 | #define HAVE_LOCALECONV 1 237 | #endif /* !defined(HAVE_LOCALECONV) */ 238 | #ifndef HAVE_LCONV_DECIMAL_POINT 239 | #define HAVE_LCONV_DECIMAL_POINT 1 240 | #endif /* HAVE_LCONV_DECIMAL_POINT */ 241 | #ifndef HAVE_LCONV_THOUSANDS_SEP 242 | #define HAVE_LCONV_THOUSANDS_SEP 1 243 | #endif /* HAVE_LCONV_THOUSANDS_SEP */ 244 | #ifndef HAVE_LONG_DOUBLE 245 | #define HAVE_LONG_DOUBLE 1 246 | #endif /* !defined(HAVE_LONG_DOUBLE) */ 247 | #ifndef HAVE_LONG_LONG_INT 248 | #define HAVE_LONG_LONG_INT 1 249 | #endif /* !defined(HAVE_LONG_LONG_INT) */ 250 | #ifndef HAVE_UNSIGNED_LONG_LONG_INT 251 | #define HAVE_UNSIGNED_LONG_LONG_INT 1 252 | #endif /* !defined(HAVE_UNSIGNED_LONG_LONG_INT) */ 253 | #ifndef HAVE_INTMAX_T 254 | #define HAVE_INTMAX_T 1 255 | #endif /* !defined(HAVE_INTMAX_T) */ 256 | #ifndef HAVE_UINTMAX_T 257 | #define HAVE_UINTMAX_T 1 258 | #endif /* !defined(HAVE_UINTMAX_T) */ 259 | #ifndef HAVE_UINTPTR_T 260 | #define HAVE_UINTPTR_T 1 261 | #endif /* !defined(HAVE_UINTPTR_T) */ 262 | #ifndef HAVE_PTRDIFF_T 263 | #define HAVE_PTRDIFF_T 1 264 | #endif /* !defined(HAVE_PTRDIFF_T) */ 265 | #ifndef HAVE_VA_COPY 266 | #define HAVE_VA_COPY 1 267 | #endif /* !defined(HAVE_VA_COPY) */ 268 | #ifndef HAVE___VA_COPY 269 | #define HAVE___VA_COPY 1 270 | #endif /* !defined(HAVE___VA_COPY) */ 271 | #endif /* HAVE_CONFIG_H */ 272 | #define snprintf rpl_snprintf 273 | #define vsnprintf rpl_vsnprintf 274 | #define asprintf rpl_asprintf 275 | #define vasprintf rpl_vasprintf 276 | #endif /* TEST_SNPRINTF */ 277 | 278 | #if !HAVE_SNPRINTF || !HAVE_VSNPRINTF || !HAVE_ASPRINTF || !HAVE_VASPRINTF 279 | #include /* For NULL, size_t, vsnprintf(3), and vasprintf(3). */ 280 | #ifdef VA_START 281 | #undef VA_START 282 | #endif /* defined(VA_START) */ 283 | #ifdef VA_SHIFT 284 | #undef VA_SHIFT 285 | #endif /* defined(VA_SHIFT) */ 286 | #if HAVE_STDARG_H 287 | #include 288 | #define VA_START(ap, last) va_start(ap, last) 289 | #define VA_SHIFT(ap, value, type) /* No-op for ANSI C. */ 290 | #else /* Assume is available. */ 291 | #include 292 | #define VA_START(ap, last) va_start(ap) /* "last" is ignored. */ 293 | #define VA_SHIFT(ap, value, type) value = va_arg(ap, type) 294 | #endif /* HAVE_STDARG_H */ 295 | 296 | #if !HAVE_VASPRINTF 297 | #if HAVE_STDLIB_H 298 | #include /* For malloc(3). */ 299 | #endif /* HAVE_STDLIB_H */ 300 | #ifdef VA_COPY 301 | #undef VA_COPY 302 | #endif /* defined(VA_COPY) */ 303 | #ifdef VA_END_COPY 304 | #undef VA_END_COPY 305 | #endif /* defined(VA_END_COPY) */ 306 | #if HAVE_VA_COPY 307 | #define VA_COPY(dest, src) va_copy(dest, src) 308 | #define VA_END_COPY(ap) va_end(ap) 309 | #elif HAVE___VA_COPY 310 | #define VA_COPY(dest, src) __va_copy(dest, src) 311 | #define VA_END_COPY(ap) va_end(ap) 312 | #else 313 | #define VA_COPY(dest, src) (void)mymemcpy(&dest, &src, sizeof(va_list)) 314 | #define VA_END_COPY(ap) /* No-op. */ 315 | #define NEED_MYMEMCPY 1 316 | static void *mymemcpy(void *, void *, size_t); 317 | #endif /* HAVE_VA_COPY */ 318 | #endif /* !HAVE_VASPRINTF */ 319 | 320 | #if !HAVE_VSNPRINTF 321 | #include /* For ERANGE and errno. */ 322 | #include /* For *_MAX. */ 323 | #if HAVE_INTTYPES_H 324 | #include /* For intmax_t (if not defined in ). */ 325 | #endif /* HAVE_INTTYPES_H */ 326 | #if HAVE_LOCALE_H 327 | #include /* For localeconv(3). */ 328 | #endif /* HAVE_LOCALE_H */ 329 | #if HAVE_STDDEF_H 330 | #include /* For ptrdiff_t. */ 331 | #endif /* HAVE_STDDEF_H */ 332 | #if HAVE_STDINT_H 333 | #include /* For intmax_t. */ 334 | #endif /* HAVE_STDINT_H */ 335 | 336 | /* Support for unsigned long long int. We may also need ULLONG_MAX. */ 337 | #ifndef ULONG_MAX /* We may need ULONG_MAX as a fallback. */ 338 | #ifdef UINT_MAX 339 | #define ULONG_MAX UINT_MAX 340 | #else 341 | #define ULONG_MAX INT_MAX 342 | #endif /* defined(UINT_MAX) */ 343 | #endif /* !defined(ULONG_MAX) */ 344 | #ifdef ULLONG 345 | #undef ULLONG 346 | #endif /* defined(ULLONG) */ 347 | #if HAVE_UNSIGNED_LONG_LONG_INT 348 | #define ULLONG unsigned long long int 349 | #ifndef ULLONG_MAX 350 | #define ULLONG_MAX ULONG_MAX 351 | #endif /* !defined(ULLONG_MAX) */ 352 | #else 353 | #define ULLONG unsigned long int 354 | #ifdef ULLONG_MAX 355 | #undef ULLONG_MAX 356 | #endif /* defined(ULLONG_MAX) */ 357 | #define ULLONG_MAX ULONG_MAX 358 | #endif /* HAVE_LONG_LONG_INT */ 359 | 360 | /* Support for uintmax_t. We also need UINTMAX_MAX. */ 361 | #ifdef UINTMAX_T 362 | #undef UINTMAX_T 363 | #endif /* defined(UINTMAX_T) */ 364 | #if HAVE_UINTMAX_T || defined(uintmax_t) 365 | #define UINTMAX_T uintmax_t 366 | #ifndef UINTMAX_MAX 367 | #define UINTMAX_MAX ULLONG_MAX 368 | #endif /* !defined(UINTMAX_MAX) */ 369 | #else 370 | #define UINTMAX_T ULLONG 371 | #ifdef UINTMAX_MAX 372 | #undef UINTMAX_MAX 373 | #endif /* defined(UINTMAX_MAX) */ 374 | #define UINTMAX_MAX ULLONG_MAX 375 | #endif /* HAVE_UINTMAX_T || defined(uintmax_t) */ 376 | 377 | /* Support for long double. */ 378 | #ifndef LDOUBLE 379 | #if HAVE_LONG_DOUBLE 380 | #define LDOUBLE long double 381 | #else 382 | #define LDOUBLE double 383 | #endif /* HAVE_LONG_DOUBLE */ 384 | #endif /* !defined(LDOUBLE) */ 385 | 386 | /* Support for long long int. */ 387 | #ifndef LLONG 388 | #if HAVE_LONG_LONG_INT 389 | #define LLONG long long int 390 | #else 391 | #define LLONG long int 392 | #endif /* HAVE_LONG_LONG_INT */ 393 | #endif /* !defined(LLONG) */ 394 | 395 | /* Support for intmax_t. */ 396 | #ifndef INTMAX_T 397 | #if HAVE_INTMAX_T || defined(intmax_t) 398 | #define INTMAX_T intmax_t 399 | #else 400 | #define INTMAX_T LLONG 401 | #endif /* HAVE_INTMAX_T || defined(intmax_t) */ 402 | #endif /* !defined(INTMAX_T) */ 403 | 404 | /* Support for uintptr_t. */ 405 | #ifndef UINTPTR_T 406 | #if HAVE_UINTPTR_T || defined(uintptr_t) 407 | #define UINTPTR_T uintptr_t 408 | #else 409 | #define UINTPTR_T unsigned long int 410 | #endif /* HAVE_UINTPTR_T || defined(uintptr_t) */ 411 | #endif /* !defined(UINTPTR_T) */ 412 | 413 | /* Support for ptrdiff_t. */ 414 | #ifndef PTRDIFF_T 415 | #if HAVE_PTRDIFF_T || defined(ptrdiff_t) 416 | #define PTRDIFF_T ptrdiff_t 417 | #else 418 | #define PTRDIFF_T long int 419 | #endif /* HAVE_PTRDIFF_T || defined(ptrdiff_t) */ 420 | #endif /* !defined(PTRDIFF_T) */ 421 | 422 | /* 423 | * We need an unsigned integer type corresponding to ptrdiff_t (cf. C99: 424 | * 7.19.6.1, 7). However, we'll simply use PTRDIFF_T and convert it to an 425 | * unsigned type if necessary. This should work just fine in practice. 426 | */ 427 | #ifndef UPTRDIFF_T 428 | #define UPTRDIFF_T PTRDIFF_T 429 | #endif /* !defined(UPTRDIFF_T) */ 430 | 431 | /* 432 | * We need a signed integer type corresponding to size_t (cf. C99: 7.19.6.1, 7). 433 | * However, we'll simply use size_t and convert it to a signed type if 434 | * necessary. This should work just fine in practice. 435 | */ 436 | #ifndef SSIZE_T 437 | #define SSIZE_T size_t 438 | #endif /* !defined(SSIZE_T) */ 439 | 440 | /* Either ERANGE or E2BIG should be available everywhere. */ 441 | #ifndef ERANGE 442 | #define ERANGE E2BIG 443 | #endif /* !defined(ERANGE) */ 444 | #ifndef EOVERFLOW 445 | #define EOVERFLOW ERANGE 446 | #endif /* !defined(EOVERFLOW) */ 447 | 448 | /* 449 | * Buffer size to hold the octal string representation of UINT128_MAX without 450 | * nul-termination ("3777777777777777777777777777777777777777777"). 451 | */ 452 | #ifdef MAX_CONVERT_LENGTH 453 | #undef MAX_CONVERT_LENGTH 454 | #endif /* defined(MAX_CONVERT_LENGTH) */ 455 | #define MAX_CONVERT_LENGTH 43 456 | 457 | /* Format read states. */ 458 | #define PRINT_S_DEFAULT 0 459 | #define PRINT_S_FLAGS 1 460 | #define PRINT_S_WIDTH 2 461 | #define PRINT_S_DOT 3 462 | #define PRINT_S_PRECISION 4 463 | #define PRINT_S_MOD 5 464 | #define PRINT_S_CONV 6 465 | 466 | /* Format flags. */ 467 | #define PRINT_F_MINUS (1 << 0) 468 | #define PRINT_F_PLUS (1 << 1) 469 | #define PRINT_F_SPACE (1 << 2) 470 | #define PRINT_F_NUM (1 << 3) 471 | #define PRINT_F_ZERO (1 << 4) 472 | #define PRINT_F_QUOTE (1 << 5) 473 | #define PRINT_F_UP (1 << 6) 474 | #define PRINT_F_UNSIGNED (1 << 7) 475 | #define PRINT_F_TYPE_G (1 << 8) 476 | #define PRINT_F_TYPE_E (1 << 9) 477 | 478 | /* Conversion flags. */ 479 | #define PRINT_C_CHAR 1 480 | #define PRINT_C_SHORT 2 481 | #define PRINT_C_LONG 3 482 | #define PRINT_C_LLONG 4 483 | #define PRINT_C_LDOUBLE 5 484 | #define PRINT_C_SIZE 6 485 | #define PRINT_C_PTRDIFF 7 486 | #define PRINT_C_INTMAX 8 487 | 488 | #ifndef MAX 489 | #define MAX(x, y) ((x >= y) ? x : y) 490 | #endif /* !defined(MAX) */ 491 | #ifndef CHARTOINT 492 | #define CHARTOINT(ch) (ch - '0') 493 | #endif /* !defined(CHARTOINT) */ 494 | #ifndef ISDIGIT 495 | #define ISDIGIT(ch) ('0' <= (unsigned char)ch && (unsigned char)ch <= '9') 496 | #endif /* !defined(ISDIGIT) */ 497 | #ifndef ISNAN 498 | #define ISNAN(x) (x != x) 499 | #endif /* !defined(ISNAN) */ 500 | #ifndef ISINF 501 | #define ISINF(x) (x != 0.0 && x + x == x) 502 | #endif /* !defined(ISINF) */ 503 | 504 | #ifdef OUTCHAR 505 | #undef OUTCHAR 506 | #endif /* defined(OUTCHAR) */ 507 | #define OUTCHAR(str, len, size, ch) \ 508 | do { \ 509 | if (len + 1 < size) \ 510 | str[len] = ch; \ 511 | (len)++; \ 512 | } while (/* CONSTCOND */ 0) 513 | 514 | static void fmtstr(char *, size_t *, size_t, const char *, int, int, int); 515 | static void fmtint(char *, size_t *, size_t, INTMAX_T, int, int, int, int); 516 | static void fmtflt(char *, size_t *, size_t, LDOUBLE, int, int, int, int *); 517 | static void printsep(char *, size_t *, size_t); 518 | static int getnumsep(int); 519 | static int getexponent(LDOUBLE); 520 | static int convert(UINTMAX_T, char *, size_t, int, int); 521 | static UINTMAX_T cast(LDOUBLE); 522 | static UINTMAX_T myround(LDOUBLE); 523 | static LDOUBLE mypow10(int); 524 | 525 | extern int errno; 526 | 527 | int 528 | rpl_vsnprintf(char *str, size_t size, const char *format, va_list args) 529 | { 530 | LDOUBLE fvalue; 531 | INTMAX_T value; 532 | unsigned char cvalue; 533 | const char *strvalue; 534 | INTMAX_T *intmaxptr; 535 | PTRDIFF_T *ptrdiffptr; 536 | SSIZE_T *sizeptr; 537 | LLONG *llongptr; 538 | long int *longptr; 539 | int *intptr; 540 | short int *shortptr; 541 | signed char *charptr; 542 | size_t len = 0; 543 | int overflow = 0; 544 | int base = 0; 545 | int cflags = 0; 546 | int flags = 0; 547 | int width = 0; 548 | int precision = -1; 549 | int state = PRINT_S_DEFAULT; 550 | char ch = *format++; 551 | 552 | /* 553 | * C99 says: "If `n' is zero, nothing is written, and `s' may be a null 554 | * pointer." (7.19.6.5, 2) We're forgiving and allow a NULL pointer 555 | * even if a size larger than zero was specified. At least NetBSD's 556 | * snprintf(3) does the same, as well as other versions of this file. 557 | * (Though some of these versions will write to a non-NULL buffer even 558 | * if a size of zero was specified, which violates the standard.) 559 | */ 560 | if (str == NULL && size != 0) 561 | size = 0; 562 | 563 | while (ch != '\0') 564 | switch (state) { 565 | case PRINT_S_DEFAULT: 566 | if (ch == '%') 567 | state = PRINT_S_FLAGS; 568 | else 569 | OUTCHAR(str, len, size, ch); 570 | ch = *format++; 571 | break; 572 | case PRINT_S_FLAGS: 573 | switch (ch) { 574 | case '-': 575 | flags |= PRINT_F_MINUS; 576 | ch = *format++; 577 | break; 578 | case '+': 579 | flags |= PRINT_F_PLUS; 580 | ch = *format++; 581 | break; 582 | case ' ': 583 | flags |= PRINT_F_SPACE; 584 | ch = *format++; 585 | break; 586 | case '#': 587 | flags |= PRINT_F_NUM; 588 | ch = *format++; 589 | break; 590 | case '0': 591 | flags |= PRINT_F_ZERO; 592 | ch = *format++; 593 | break; 594 | case '\'': /* SUSv2 flag (not in C99). */ 595 | flags |= PRINT_F_QUOTE; 596 | ch = *format++; 597 | break; 598 | default: 599 | state = PRINT_S_WIDTH; 600 | break; 601 | } 602 | break; 603 | case PRINT_S_WIDTH: 604 | if (ISDIGIT(ch)) { 605 | ch = CHARTOINT(ch); 606 | if (width > (INT_MAX - ch) / 10) { 607 | overflow = 1; 608 | goto out; 609 | } 610 | width = 10 * width + ch; 611 | ch = *format++; 612 | } else if (ch == '*') { 613 | /* 614 | * C99 says: "A negative field width argument is 615 | * taken as a `-' flag followed by a positive 616 | * field width." (7.19.6.1, 5) 617 | */ 618 | if ((width = va_arg(args, int)) < 0) { 619 | flags |= PRINT_F_MINUS; 620 | width = -width; 621 | } 622 | ch = *format++; 623 | state = PRINT_S_DOT; 624 | } else 625 | state = PRINT_S_DOT; 626 | break; 627 | case PRINT_S_DOT: 628 | if (ch == '.') { 629 | state = PRINT_S_PRECISION; 630 | ch = *format++; 631 | } else 632 | state = PRINT_S_MOD; 633 | break; 634 | case PRINT_S_PRECISION: 635 | if (precision == -1) 636 | precision = 0; 637 | if (ISDIGIT(ch)) { 638 | ch = CHARTOINT(ch); 639 | if (precision > (INT_MAX - ch) / 10) { 640 | overflow = 1; 641 | goto out; 642 | } 643 | precision = 10 * precision + ch; 644 | ch = *format++; 645 | } else if (ch == '*') { 646 | /* 647 | * C99 says: "A negative precision argument is 648 | * taken as if the precision were omitted." 649 | * (7.19.6.1, 5) 650 | */ 651 | if ((precision = va_arg(args, int)) < 0) 652 | precision = -1; 653 | ch = *format++; 654 | state = PRINT_S_MOD; 655 | } else 656 | state = PRINT_S_MOD; 657 | break; 658 | case PRINT_S_MOD: 659 | switch (ch) { 660 | case 'h': 661 | ch = *format++; 662 | if (ch == 'h') { /* It's a char. */ 663 | ch = *format++; 664 | cflags = PRINT_C_CHAR; 665 | } else 666 | cflags = PRINT_C_SHORT; 667 | break; 668 | case 'l': 669 | ch = *format++; 670 | if (ch == 'l') { /* It's a long long. */ 671 | ch = *format++; 672 | cflags = PRINT_C_LLONG; 673 | } else 674 | cflags = PRINT_C_LONG; 675 | break; 676 | case 'L': 677 | cflags = PRINT_C_LDOUBLE; 678 | ch = *format++; 679 | break; 680 | case 'j': 681 | cflags = PRINT_C_INTMAX; 682 | ch = *format++; 683 | break; 684 | case 't': 685 | cflags = PRINT_C_PTRDIFF; 686 | ch = *format++; 687 | break; 688 | case 'z': 689 | cflags = PRINT_C_SIZE; 690 | ch = *format++; 691 | break; 692 | } 693 | state = PRINT_S_CONV; 694 | break; 695 | case PRINT_S_CONV: 696 | switch (ch) { 697 | case 'd': 698 | /* FALLTHROUGH */ 699 | case 'i': 700 | switch (cflags) { 701 | case PRINT_C_CHAR: 702 | value = (signed char)va_arg(args, int); 703 | break; 704 | case PRINT_C_SHORT: 705 | value = (short int)va_arg(args, int); 706 | break; 707 | case PRINT_C_LONG: 708 | value = va_arg(args, long int); 709 | break; 710 | case PRINT_C_LLONG: 711 | value = va_arg(args, LLONG); 712 | break; 713 | case PRINT_C_SIZE: 714 | value = va_arg(args, SSIZE_T); 715 | break; 716 | case PRINT_C_INTMAX: 717 | value = va_arg(args, INTMAX_T); 718 | break; 719 | case PRINT_C_PTRDIFF: 720 | value = va_arg(args, PTRDIFF_T); 721 | break; 722 | default: 723 | value = va_arg(args, int); 724 | break; 725 | } 726 | fmtint(str, &len, size, value, 10, width, 727 | precision, flags); 728 | break; 729 | case 'X': 730 | flags |= PRINT_F_UP; 731 | /* FALLTHROUGH */ 732 | case 'x': 733 | base = 16; 734 | /* FALLTHROUGH */ 735 | case 'o': 736 | if (base == 0) 737 | base = 8; 738 | /* FALLTHROUGH */ 739 | case 'u': 740 | if (base == 0) 741 | base = 10; 742 | flags |= PRINT_F_UNSIGNED; 743 | switch (cflags) { 744 | case PRINT_C_CHAR: 745 | value = (unsigned char)va_arg(args, 746 | unsigned int); 747 | break; 748 | case PRINT_C_SHORT: 749 | value = (unsigned short int)va_arg(args, 750 | unsigned int); 751 | break; 752 | case PRINT_C_LONG: 753 | value = va_arg(args, unsigned long int); 754 | break; 755 | case PRINT_C_LLONG: 756 | value = va_arg(args, ULLONG); 757 | break; 758 | case PRINT_C_SIZE: 759 | value = va_arg(args, size_t); 760 | break; 761 | case PRINT_C_INTMAX: 762 | value = va_arg(args, UINTMAX_T); 763 | break; 764 | case PRINT_C_PTRDIFF: 765 | value = va_arg(args, UPTRDIFF_T); 766 | break; 767 | default: 768 | value = va_arg(args, unsigned int); 769 | break; 770 | } 771 | fmtint(str, &len, size, value, base, width, 772 | precision, flags); 773 | break; 774 | case 'A': 775 | /* Not yet supported, we'll use "%F". */ 776 | /* FALLTHROUGH */ 777 | case 'F': 778 | flags |= PRINT_F_UP; 779 | /* FALLTHROUGH */ 780 | case 'a': 781 | /* Not yet supported, we'll use "%f". */ 782 | /* FALLTHROUGH */ 783 | case 'f': 784 | if (cflags == PRINT_C_LDOUBLE) 785 | fvalue = va_arg(args, LDOUBLE); 786 | else 787 | fvalue = va_arg(args, double); 788 | fmtflt(str, &len, size, fvalue, width, 789 | precision, flags, &overflow); 790 | if (overflow) 791 | goto out; 792 | break; 793 | case 'E': 794 | flags |= PRINT_F_UP; 795 | /* FALLTHROUGH */ 796 | case 'e': 797 | flags |= PRINT_F_TYPE_E; 798 | if (cflags == PRINT_C_LDOUBLE) 799 | fvalue = va_arg(args, LDOUBLE); 800 | else 801 | fvalue = va_arg(args, double); 802 | fmtflt(str, &len, size, fvalue, width, 803 | precision, flags, &overflow); 804 | if (overflow) 805 | goto out; 806 | break; 807 | case 'G': 808 | flags |= PRINT_F_UP; 809 | /* FALLTHROUGH */ 810 | case 'g': 811 | flags |= PRINT_F_TYPE_G; 812 | if (cflags == PRINT_C_LDOUBLE) 813 | fvalue = va_arg(args, LDOUBLE); 814 | else 815 | fvalue = va_arg(args, double); 816 | /* 817 | * If the precision is zero, it is treated as 818 | * one (cf. C99: 7.19.6.1, 8). 819 | */ 820 | if (precision == 0) 821 | precision = 1; 822 | fmtflt(str, &len, size, fvalue, width, 823 | precision, flags, &overflow); 824 | if (overflow) 825 | goto out; 826 | break; 827 | case 'c': 828 | cvalue = va_arg(args, int); 829 | OUTCHAR(str, len, size, cvalue); 830 | break; 831 | case 's': 832 | strvalue = va_arg(args, char *); 833 | fmtstr(str, &len, size, strvalue, width, 834 | precision, flags); 835 | break; 836 | case 'p': 837 | /* 838 | * C99 says: "The value of the pointer is 839 | * converted to a sequence of printing 840 | * characters, in an implementation-defined 841 | * manner." (C99: 7.19.6.1, 8) 842 | */ 843 | if ((strvalue = va_arg(args, void *)) == NULL) 844 | /* 845 | * We use the glibc format. BSD prints 846 | * "0x0", SysV "0". 847 | */ 848 | fmtstr(str, &len, size, "(nil)", width, 849 | -1, flags); 850 | else { 851 | /* 852 | * We use the BSD/glibc format. SysV 853 | * omits the "0x" prefix (which we emit 854 | * using the PRINT_F_NUM flag). 855 | */ 856 | flags |= PRINT_F_NUM; 857 | flags |= PRINT_F_UNSIGNED; 858 | fmtint(str, &len, size, 859 | (UINTPTR_T)strvalue, 16, width, 860 | precision, flags); 861 | } 862 | break; 863 | case 'n': 864 | switch (cflags) { 865 | case PRINT_C_CHAR: 866 | charptr = va_arg(args, signed char *); 867 | *charptr = len; 868 | break; 869 | case PRINT_C_SHORT: 870 | shortptr = va_arg(args, short int *); 871 | *shortptr = len; 872 | break; 873 | case PRINT_C_LONG: 874 | longptr = va_arg(args, long int *); 875 | *longptr = len; 876 | break; 877 | case PRINT_C_LLONG: 878 | llongptr = va_arg(args, LLONG *); 879 | *llongptr = len; 880 | break; 881 | case PRINT_C_SIZE: 882 | /* 883 | * C99 says that with the "z" length 884 | * modifier, "a following `n' conversion 885 | * specifier applies to a pointer to a 886 | * signed integer type corresponding to 887 | * size_t argument." (7.19.6.1, 7) 888 | */ 889 | sizeptr = va_arg(args, SSIZE_T *); 890 | *sizeptr = len; 891 | break; 892 | case PRINT_C_INTMAX: 893 | intmaxptr = va_arg(args, INTMAX_T *); 894 | *intmaxptr = len; 895 | break; 896 | case PRINT_C_PTRDIFF: 897 | ptrdiffptr = va_arg(args, PTRDIFF_T *); 898 | *ptrdiffptr = len; 899 | break; 900 | default: 901 | intptr = va_arg(args, int *); 902 | *intptr = len; 903 | break; 904 | } 905 | break; 906 | case '%': /* Print a "%" character verbatim. */ 907 | OUTCHAR(str, len, size, ch); 908 | break; 909 | default: /* Skip other characters. */ 910 | break; 911 | } 912 | ch = *format++; 913 | state = PRINT_S_DEFAULT; 914 | base = cflags = flags = width = 0; 915 | precision = -1; 916 | break; 917 | } 918 | out: 919 | if (len < size) 920 | str[len] = '\0'; 921 | else if (size > 0) 922 | str[size - 1] = '\0'; 923 | 924 | if (overflow || len >= INT_MAX) { 925 | errno = overflow ? EOVERFLOW : ERANGE; 926 | return -1; 927 | } 928 | return (int)len; 929 | } 930 | 931 | static void 932 | fmtstr(char *str, size_t *len, size_t size, const char *value, int width, 933 | int precision, int flags) 934 | { 935 | int padlen, strln; /* Amount to pad. */ 936 | int noprecision = (precision == -1); 937 | 938 | if (value == NULL) /* We're forgiving. */ 939 | value = "(null)"; 940 | 941 | /* If a precision was specified, don't read the string past it. */ 942 | for (strln = 0; value[strln] != '\0' && 943 | (noprecision || strln < precision); strln++) 944 | continue; 945 | 946 | if ((padlen = width - strln) < 0) 947 | padlen = 0; 948 | if (flags & PRINT_F_MINUS) /* Left justify. */ 949 | padlen = -padlen; 950 | 951 | while (padlen > 0) { /* Leading spaces. */ 952 | OUTCHAR(str, *len, size, ' '); 953 | padlen--; 954 | } 955 | while (*value != '\0' && (noprecision || precision-- > 0)) { 956 | OUTCHAR(str, *len, size, *value); 957 | value++; 958 | } 959 | while (padlen < 0) { /* Trailing spaces. */ 960 | OUTCHAR(str, *len, size, ' '); 961 | padlen++; 962 | } 963 | } 964 | 965 | static void 966 | fmtint(char *str, size_t *len, size_t size, INTMAX_T value, int base, int width, 967 | int precision, int flags) 968 | { 969 | UINTMAX_T uvalue; 970 | char iconvert[MAX_CONVERT_LENGTH]; 971 | char sign = 0; 972 | char hexprefix = 0; 973 | int spadlen = 0; /* Amount to space pad. */ 974 | int zpadlen = 0; /* Amount to zero pad. */ 975 | int pos; 976 | int separators = (flags & PRINT_F_QUOTE); 977 | int noprecision = (precision == -1); 978 | 979 | if (flags & PRINT_F_UNSIGNED) 980 | uvalue = value; 981 | else { 982 | uvalue = (value >= 0) ? value : -value; 983 | if (value < 0) 984 | sign = '-'; 985 | else if (flags & PRINT_F_PLUS) /* Do a sign. */ 986 | sign = '+'; 987 | else if (flags & PRINT_F_SPACE) 988 | sign = ' '; 989 | } 990 | 991 | pos = convert(uvalue, iconvert, sizeof(iconvert), base, 992 | flags & PRINT_F_UP); 993 | 994 | if (flags & PRINT_F_NUM && uvalue != 0) { 995 | /* 996 | * C99 says: "The result is converted to an `alternative form'. 997 | * For `o' conversion, it increases the precision, if and only 998 | * if necessary, to force the first digit of the result to be a 999 | * zero (if the value and precision are both 0, a single 0 is 1000 | * printed). For `x' (or `X') conversion, a nonzero result has 1001 | * `0x' (or `0X') prefixed to it." (7.19.6.1, 6) 1002 | */ 1003 | switch (base) { 1004 | case 8: 1005 | if (precision <= pos) 1006 | precision = pos + 1; 1007 | break; 1008 | case 16: 1009 | hexprefix = (flags & PRINT_F_UP) ? 'X' : 'x'; 1010 | break; 1011 | } 1012 | } 1013 | 1014 | if (separators) /* Get the number of group separators we'll print. */ 1015 | separators = getnumsep(pos); 1016 | 1017 | zpadlen = precision - pos - separators; 1018 | spadlen = width /* Minimum field width. */ 1019 | - separators /* Number of separators. */ 1020 | - MAX(precision, pos) /* Number of integer digits. */ 1021 | - ((sign != 0) ? 1 : 0) /* Will we print a sign? */ 1022 | - ((hexprefix != 0) ? 2 : 0); /* Will we print a prefix? */ 1023 | 1024 | if (zpadlen < 0) 1025 | zpadlen = 0; 1026 | if (spadlen < 0) 1027 | spadlen = 0; 1028 | 1029 | /* 1030 | * C99 says: "If the `0' and `-' flags both appear, the `0' flag is 1031 | * ignored. For `d', `i', `o', `u', `x', and `X' conversions, if a 1032 | * precision is specified, the `0' flag is ignored." (7.19.6.1, 6) 1033 | */ 1034 | if (flags & PRINT_F_MINUS) /* Left justify. */ 1035 | spadlen = -spadlen; 1036 | else if (flags & PRINT_F_ZERO && noprecision) { 1037 | zpadlen += spadlen; 1038 | spadlen = 0; 1039 | } 1040 | while (spadlen > 0) { /* Leading spaces. */ 1041 | OUTCHAR(str, *len, size, ' '); 1042 | spadlen--; 1043 | } 1044 | if (sign != 0) /* Sign. */ 1045 | OUTCHAR(str, *len, size, sign); 1046 | if (hexprefix != 0) { /* A "0x" or "0X" prefix. */ 1047 | OUTCHAR(str, *len, size, '0'); 1048 | OUTCHAR(str, *len, size, hexprefix); 1049 | } 1050 | while (zpadlen > 0) { /* Leading zeros. */ 1051 | OUTCHAR(str, *len, size, '0'); 1052 | zpadlen--; 1053 | } 1054 | while (pos > 0) { /* The actual digits. */ 1055 | pos--; 1056 | OUTCHAR(str, *len, size, iconvert[pos]); 1057 | if (separators > 0 && pos > 0 && pos % 3 == 0) 1058 | printsep(str, len, size); 1059 | } 1060 | while (spadlen < 0) { /* Trailing spaces. */ 1061 | OUTCHAR(str, *len, size, ' '); 1062 | spadlen++; 1063 | } 1064 | } 1065 | 1066 | static void 1067 | fmtflt(char *str, size_t *len, size_t size, LDOUBLE fvalue, int width, 1068 | int precision, int flags, int *overflow) 1069 | { 1070 | LDOUBLE ufvalue; 1071 | UINTMAX_T intpart; 1072 | UINTMAX_T fracpart; 1073 | UINTMAX_T mask; 1074 | const char *infnan = NULL; 1075 | char iconvert[MAX_CONVERT_LENGTH]; 1076 | char fconvert[MAX_CONVERT_LENGTH]; 1077 | char econvert[4]; /* "e-12" (without nul-termination). */ 1078 | char esign = 0; 1079 | char sign = 0; 1080 | int leadfraczeros = 0; 1081 | int exponent = 0; 1082 | int emitpoint = 0; 1083 | int omitzeros = 0; 1084 | int omitcount = 0; 1085 | int padlen = 0; 1086 | int epos = 0; 1087 | int fpos = 0; 1088 | int ipos = 0; 1089 | int separators = (flags & PRINT_F_QUOTE); 1090 | int estyle = (flags & PRINT_F_TYPE_E); 1091 | #if HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT 1092 | struct lconv *lc = localeconv(); 1093 | #endif /* HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT */ 1094 | 1095 | /* 1096 | * AIX' man page says the default is 0, but C99 and at least Solaris' 1097 | * and NetBSD's man pages say the default is 6, and sprintf(3) on AIX 1098 | * defaults to 6. 1099 | */ 1100 | if (precision == -1) 1101 | precision = 6; 1102 | 1103 | if (fvalue < 0.0) 1104 | sign = '-'; 1105 | else if (flags & PRINT_F_PLUS) /* Do a sign. */ 1106 | sign = '+'; 1107 | else if (flags & PRINT_F_SPACE) 1108 | sign = ' '; 1109 | 1110 | if (ISNAN(fvalue)) 1111 | infnan = (flags & PRINT_F_UP) ? "NAN" : "nan"; 1112 | else if (ISINF(fvalue)) 1113 | infnan = (flags & PRINT_F_UP) ? "INF" : "inf"; 1114 | 1115 | if (infnan != NULL) { 1116 | if (sign != 0) 1117 | iconvert[ipos++] = sign; 1118 | while (*infnan != '\0') 1119 | iconvert[ipos++] = *infnan++; 1120 | fmtstr(str, len, size, iconvert, width, ipos, flags); 1121 | return; 1122 | } 1123 | 1124 | /* "%e" (or "%E") or "%g" (or "%G") conversion. */ 1125 | if (flags & PRINT_F_TYPE_E || flags & PRINT_F_TYPE_G) { 1126 | if (flags & PRINT_F_TYPE_G) { 1127 | /* 1128 | * For "%g" (and "%G") conversions, the precision 1129 | * specifies the number of significant digits, which 1130 | * includes the digits in the integer part. The 1131 | * conversion will or will not be using "e-style" (like 1132 | * "%e" or "%E" conversions) depending on the precision 1133 | * and on the exponent. However, the exponent can be 1134 | * affected by rounding the converted value, so we'll 1135 | * leave this decision for later. Until then, we'll 1136 | * assume that we're going to do an "e-style" conversion 1137 | * (in order to get the exponent calculated). For 1138 | * "e-style", the precision must be decremented by one. 1139 | */ 1140 | precision--; 1141 | /* 1142 | * For "%g" (and "%G") conversions, trailing zeros are 1143 | * removed from the fractional portion of the result 1144 | * unless the "#" flag was specified. 1145 | */ 1146 | if (!(flags & PRINT_F_NUM)) 1147 | omitzeros = 1; 1148 | } 1149 | exponent = getexponent(fvalue); 1150 | estyle = 1; 1151 | } 1152 | 1153 | again: 1154 | /* 1155 | * Sorry, we only support 9, 19, or 38 digits (that is, the number of 1156 | * digits of the 32-bit, the 64-bit, or the 128-bit UINTMAX_MAX value 1157 | * minus one) past the decimal point due to our conversion method. 1158 | */ 1159 | switch (sizeof(UINTMAX_T)) { 1160 | case 16: 1161 | if (precision > 38) 1162 | precision = 38; 1163 | break; 1164 | case 8: 1165 | if (precision > 19) 1166 | precision = 19; 1167 | break; 1168 | default: 1169 | if (precision > 9) 1170 | precision = 9; 1171 | break; 1172 | } 1173 | 1174 | ufvalue = (fvalue >= 0.0) ? fvalue : -fvalue; 1175 | if (estyle) /* We want exactly one integer digit. */ 1176 | ufvalue /= mypow10(exponent); 1177 | 1178 | if ((intpart = cast(ufvalue)) == UINTMAX_MAX) { 1179 | *overflow = 1; 1180 | return; 1181 | } 1182 | 1183 | /* 1184 | * Factor of ten with the number of digits needed for the fractional 1185 | * part. For example, if the precision is 3, the mask will be 1000. 1186 | */ 1187 | mask = mypow10(precision); 1188 | /* 1189 | * We "cheat" by converting the fractional part to integer by 1190 | * multiplying by a factor of ten. 1191 | */ 1192 | if ((fracpart = myround(mask * (ufvalue - intpart))) >= mask) { 1193 | /* 1194 | * For example, ufvalue = 2.99962, intpart = 2, and mask = 1000 1195 | * (because precision = 3). Now, myround(1000 * 0.99962) will 1196 | * return 1000. So, the integer part must be incremented by one 1197 | * and the fractional part must be set to zero. 1198 | */ 1199 | intpart++; 1200 | fracpart = 0; 1201 | if (estyle && intpart == 10) { 1202 | /* 1203 | * The value was rounded up to ten, but we only want one 1204 | * integer digit if using "e-style". So, the integer 1205 | * part must be set to one and the exponent must be 1206 | * incremented by one. 1207 | */ 1208 | intpart = 1; 1209 | exponent++; 1210 | } 1211 | } 1212 | 1213 | /* 1214 | * Now that we know the real exponent, we can check whether or not to 1215 | * use "e-style" for "%g" (and "%G") conversions. If we don't need 1216 | * "e-style", the precision must be adjusted and the integer and 1217 | * fractional parts must be recalculated from the original value. 1218 | * 1219 | * C99 says: "Let P equal the precision if nonzero, 6 if the precision 1220 | * is omitted, or 1 if the precision is zero. Then, if a conversion 1221 | * with style `E' would have an exponent of X: 1222 | * 1223 | * - if P > X >= -4, the conversion is with style `f' (or `F') and 1224 | * precision P - (X + 1). 1225 | * 1226 | * - otherwise, the conversion is with style `e' (or `E') and precision 1227 | * P - 1." (7.19.6.1, 8) 1228 | * 1229 | * Note that we had decremented the precision by one. 1230 | */ 1231 | if (flags & PRINT_F_TYPE_G && estyle && 1232 | precision + 1 > exponent && exponent >= -4) { 1233 | precision -= exponent; 1234 | estyle = 0; 1235 | goto again; 1236 | } 1237 | 1238 | if (estyle) { 1239 | if (exponent < 0) { 1240 | exponent = -exponent; 1241 | esign = '-'; 1242 | } else 1243 | esign = '+'; 1244 | 1245 | /* 1246 | * Convert the exponent. The sizeof(econvert) is 4. So, the 1247 | * econvert buffer can hold e.g. "e+99" and "e-99". We don't 1248 | * support an exponent which contains more than two digits. 1249 | * Therefore, the following stores are safe. 1250 | */ 1251 | epos = convert(exponent, econvert, 2, 10, 0); 1252 | /* 1253 | * C99 says: "The exponent always contains at least two digits, 1254 | * and only as many more digits as necessary to represent the 1255 | * exponent." (7.19.6.1, 8) 1256 | */ 1257 | if (epos == 1) 1258 | econvert[epos++] = '0'; 1259 | econvert[epos++] = esign; 1260 | econvert[epos++] = (flags & PRINT_F_UP) ? 'E' : 'e'; 1261 | } 1262 | 1263 | /* Convert the integer part and the fractional part. */ 1264 | ipos = convert(intpart, iconvert, sizeof(iconvert), 10, 0); 1265 | if (fracpart != 0) /* convert() would return 1 if fracpart == 0. */ 1266 | fpos = convert(fracpart, fconvert, sizeof(fconvert), 10, 0); 1267 | 1268 | leadfraczeros = precision - fpos; 1269 | 1270 | if (omitzeros) { 1271 | if (fpos > 0) /* Omit trailing fractional part zeros. */ 1272 | while (omitcount < fpos && fconvert[omitcount] == '0') 1273 | omitcount++; 1274 | else { /* The fractional part is zero, omit it completely. */ 1275 | omitcount = precision; 1276 | leadfraczeros = 0; 1277 | } 1278 | precision -= omitcount; 1279 | } 1280 | 1281 | /* 1282 | * Print a decimal point if either the fractional part is non-zero 1283 | * and/or the "#" flag was specified. 1284 | */ 1285 | if (precision > 0 || flags & PRINT_F_NUM) 1286 | emitpoint = 1; 1287 | if (separators) /* Get the number of group separators we'll print. */ 1288 | separators = getnumsep(ipos); 1289 | 1290 | padlen = width /* Minimum field width. */ 1291 | - ipos /* Number of integer digits. */ 1292 | - epos /* Number of exponent characters. */ 1293 | - precision /* Number of fractional digits. */ 1294 | - separators /* Number of group separators. */ 1295 | - (emitpoint ? 1 : 0) /* Will we print a decimal point? */ 1296 | - ((sign != 0) ? 1 : 0); /* Will we print a sign character? */ 1297 | 1298 | if (padlen < 0) 1299 | padlen = 0; 1300 | 1301 | /* 1302 | * C99 says: "If the `0' and `-' flags both appear, the `0' flag is 1303 | * ignored." (7.19.6.1, 6) 1304 | */ 1305 | if (flags & PRINT_F_MINUS) /* Left justifty. */ 1306 | padlen = -padlen; 1307 | else if (flags & PRINT_F_ZERO && padlen > 0) { 1308 | if (sign != 0) { /* Sign. */ 1309 | OUTCHAR(str, *len, size, sign); 1310 | sign = 0; 1311 | } 1312 | while (padlen > 0) { /* Leading zeros. */ 1313 | OUTCHAR(str, *len, size, '0'); 1314 | padlen--; 1315 | } 1316 | } 1317 | while (padlen > 0) { /* Leading spaces. */ 1318 | OUTCHAR(str, *len, size, ' '); 1319 | padlen--; 1320 | } 1321 | if (sign != 0) /* Sign. */ 1322 | OUTCHAR(str, *len, size, sign); 1323 | while (ipos > 0) { /* Integer part. */ 1324 | ipos--; 1325 | OUTCHAR(str, *len, size, iconvert[ipos]); 1326 | if (separators > 0 && ipos > 0 && ipos % 3 == 0) 1327 | printsep(str, len, size); 1328 | } 1329 | if (emitpoint) { /* Decimal point. */ 1330 | #if HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT 1331 | if (lc->decimal_point != NULL && *lc->decimal_point != '\0') 1332 | OUTCHAR(str, *len, size, *lc->decimal_point); 1333 | else /* We'll always print some decimal point character. */ 1334 | #endif /* HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT */ 1335 | OUTCHAR(str, *len, size, '.'); 1336 | } 1337 | while (leadfraczeros > 0) { /* Leading fractional part zeros. */ 1338 | OUTCHAR(str, *len, size, '0'); 1339 | leadfraczeros--; 1340 | } 1341 | while (fpos > omitcount) { /* The remaining fractional part. */ 1342 | fpos--; 1343 | OUTCHAR(str, *len, size, fconvert[fpos]); 1344 | } 1345 | while (epos > 0) { /* Exponent. */ 1346 | epos--; 1347 | OUTCHAR(str, *len, size, econvert[epos]); 1348 | } 1349 | while (padlen < 0) { /* Trailing spaces. */ 1350 | OUTCHAR(str, *len, size, ' '); 1351 | padlen++; 1352 | } 1353 | } 1354 | 1355 | static void 1356 | printsep(char *str, size_t *len, size_t size) 1357 | { 1358 | #if HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP 1359 | struct lconv *lc = localeconv(); 1360 | int i; 1361 | 1362 | if (lc->thousands_sep != NULL) 1363 | for (i = 0; lc->thousands_sep[i] != '\0'; i++) 1364 | OUTCHAR(str, *len, size, lc->thousands_sep[i]); 1365 | else 1366 | #endif /* HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP */ 1367 | OUTCHAR(str, *len, size, ','); 1368 | } 1369 | 1370 | static int 1371 | getnumsep(int digits) 1372 | { 1373 | int separators = (digits - ((digits % 3 == 0) ? 1 : 0)) / 3; 1374 | #if HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP 1375 | int strln; 1376 | struct lconv *lc = localeconv(); 1377 | 1378 | /* We support an arbitrary separator length (including zero). */ 1379 | if (lc->thousands_sep != NULL) { 1380 | for (strln = 0; lc->thousands_sep[strln] != '\0'; strln++) 1381 | continue; 1382 | separators *= strln; 1383 | } 1384 | #endif /* HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP */ 1385 | return separators; 1386 | } 1387 | 1388 | static int 1389 | getexponent(LDOUBLE value) 1390 | { 1391 | LDOUBLE tmp = (value >= 0.0) ? value : -value; 1392 | int exponent = 0; 1393 | 1394 | /* 1395 | * We check for 99 > exponent > -99 in order to work around possible 1396 | * endless loops which could happen (at least) in the second loop (at 1397 | * least) if we're called with an infinite value. However, we checked 1398 | * for infinity before calling this function using our ISINF() macro, so 1399 | * this might be somewhat paranoid. 1400 | */ 1401 | while (tmp < 1.0 && tmp > 0.0 && --exponent > -99) 1402 | tmp *= 10; 1403 | while (tmp >= 10.0 && ++exponent < 99) 1404 | tmp /= 10; 1405 | 1406 | return exponent; 1407 | } 1408 | 1409 | static int 1410 | convert(UINTMAX_T value, char *buf, size_t size, int base, int caps) 1411 | { 1412 | const char *digits = caps ? "0123456789ABCDEF" : "0123456789abcdef"; 1413 | size_t pos = 0; 1414 | 1415 | /* We return an unterminated buffer with the digits in reverse order. */ 1416 | do { 1417 | buf[pos++] = digits[value % base]; 1418 | value /= base; 1419 | } while (value != 0 && pos < size); 1420 | 1421 | return (int)pos; 1422 | } 1423 | 1424 | static UINTMAX_T 1425 | cast(LDOUBLE value) 1426 | { 1427 | UINTMAX_T result; 1428 | 1429 | /* 1430 | * We check for ">=" and not for ">" because if UINTMAX_MAX cannot be 1431 | * represented exactly as an LDOUBLE value (but is less than LDBL_MAX), 1432 | * it may be increased to the nearest higher representable value for the 1433 | * comparison (cf. C99: 6.3.1.4, 2). It might then equal the LDOUBLE 1434 | * value although converting the latter to UINTMAX_T would overflow. 1435 | */ 1436 | if ((UINTMAX_T)value >= UINTMAX_MAX) 1437 | return UINTMAX_MAX; 1438 | 1439 | result = value; 1440 | /* 1441 | * At least on NetBSD/sparc64 3.0.2 and 4.99.30, casting long double to 1442 | * an integer type converts e.g. 1.9 to 2 instead of 1 (which violates 1443 | * the standard). Sigh. 1444 | */ 1445 | return (result <= value) ? result : result - 1; 1446 | } 1447 | 1448 | static UINTMAX_T 1449 | myround(LDOUBLE value) 1450 | { 1451 | UINTMAX_T intpart = cast(value); 1452 | 1453 | return ((value -= intpart) < 0.5) ? intpart : intpart + 1; 1454 | } 1455 | 1456 | static LDOUBLE 1457 | mypow10(int exponent) 1458 | { 1459 | LDOUBLE result = 1; 1460 | 1461 | while (exponent > 0) { 1462 | result *= 10; 1463 | exponent--; 1464 | } 1465 | while (exponent < 0) { 1466 | result /= 10; 1467 | exponent++; 1468 | } 1469 | return result; 1470 | } 1471 | #endif /* !HAVE_VSNPRINTF */ 1472 | 1473 | #if !HAVE_VASPRINTF 1474 | #if NEED_MYMEMCPY 1475 | void * 1476 | mymemcpy(void *dst, void *src, size_t len) 1477 | { 1478 | const char *from = src; 1479 | char *to = dst; 1480 | 1481 | /* No need for optimization, we use this only to replace va_copy(3). */ 1482 | while (len-- > 0) 1483 | *to++ = *from++; 1484 | return dst; 1485 | } 1486 | #endif /* NEED_MYMEMCPY */ 1487 | 1488 | int 1489 | rpl_vasprintf(char **ret, const char *format, va_list ap) 1490 | { 1491 | size_t size; 1492 | int len; 1493 | va_list aq; 1494 | 1495 | VA_COPY(aq, ap); 1496 | len = vsnprintf(NULL, 0, format, aq); 1497 | VA_END_COPY(aq); 1498 | if (len < 0 || (*ret = malloc(size = len + 1)) == NULL) 1499 | return -1; 1500 | return vsnprintf(*ret, size, format, ap); 1501 | } 1502 | #endif /* !HAVE_VASPRINTF */ 1503 | 1504 | #if !HAVE_SNPRINTF 1505 | #if HAVE_STDARG_H 1506 | int 1507 | rpl_snprintf(char *str, size_t size, const char *format, ...) 1508 | #else 1509 | int 1510 | rpl_snprintf(va_alist) va_dcl 1511 | #endif /* HAVE_STDARG_H */ 1512 | { 1513 | #if !HAVE_STDARG_H 1514 | char *str; 1515 | size_t size; 1516 | char *format; 1517 | #endif /* HAVE_STDARG_H */ 1518 | va_list ap; 1519 | int len; 1520 | 1521 | VA_START(ap, format); 1522 | VA_SHIFT(ap, str, char *); 1523 | VA_SHIFT(ap, size, size_t); 1524 | VA_SHIFT(ap, format, const char *); 1525 | len = vsnprintf(str, size, format, ap); 1526 | va_end(ap); 1527 | return len; 1528 | } 1529 | #endif /* !HAVE_SNPRINTF */ 1530 | 1531 | #if !HAVE_ASPRINTF 1532 | #if HAVE_STDARG_H 1533 | int 1534 | rpl_asprintf(char **ret, const char *format, ...) 1535 | #else 1536 | int 1537 | rpl_asprintf(va_alist) va_dcl 1538 | #endif /* HAVE_STDARG_H */ 1539 | { 1540 | #if !HAVE_STDARG_H 1541 | char **ret; 1542 | char *format; 1543 | #endif /* HAVE_STDARG_H */ 1544 | va_list ap; 1545 | int len; 1546 | 1547 | VA_START(ap, format); 1548 | VA_SHIFT(ap, ret, char **); 1549 | VA_SHIFT(ap, format, const char *); 1550 | len = vasprintf(ret, format, ap); 1551 | va_end(ap); 1552 | return len; 1553 | } 1554 | #endif /* !HAVE_ASPRINTF */ 1555 | #else /* Dummy declaration to avoid empty translation unit warnings. */ 1556 | int main(void); 1557 | #endif /* !HAVE_SNPRINTF || !HAVE_VSNPRINTF || !HAVE_ASPRINTF || [...] */ 1558 | 1559 | #if TEST_SNPRINTF 1560 | int 1561 | main(void) 1562 | { 1563 | const char *float_fmt[] = { 1564 | /* "%E" and "%e" formats. */ 1565 | #if HAVE_LONG_LONG_INT && !OS_BSD && !OS_IRIX 1566 | "%.16e", 1567 | "%22.16e", 1568 | "%022.16e", 1569 | "%-22.16e", 1570 | "%#+'022.16e", 1571 | #endif /* HAVE_LONG_LONG_INT && !OS_BSD && !OS_IRIX */ 1572 | "foo|%#+0123.9E|bar", 1573 | "%-123.9e", 1574 | "%123.9e", 1575 | "%+23.9e", 1576 | "%+05.8e", 1577 | "%-05.8e", 1578 | "%05.8e", 1579 | "%+5.8e", 1580 | "%-5.8e", 1581 | "% 5.8e", 1582 | "%5.8e", 1583 | "%+4.9e", 1584 | #if !OS_LINUX /* glibc sometimes gets these wrong. */ 1585 | "%+#010.0e", 1586 | "%#10.1e", 1587 | "%10.5e", 1588 | "% 10.5e", 1589 | "%5.0e", 1590 | "%5.e", 1591 | "%#5.0e", 1592 | "%#5.e", 1593 | "%3.2e", 1594 | "%3.1e", 1595 | "%-1.5e", 1596 | "%1.5e", 1597 | "%01.3e", 1598 | "%1.e", 1599 | "%.1e", 1600 | "%#.0e", 1601 | "%+.0e", 1602 | "% .0e", 1603 | "%.0e", 1604 | "%#.e", 1605 | "%+.e", 1606 | "% .e", 1607 | "%.e", 1608 | "%4e", 1609 | "%e", 1610 | "%E", 1611 | #endif /* !OS_LINUX */ 1612 | /* "%F" and "%f" formats. */ 1613 | #if !OS_BSD && !OS_IRIX 1614 | "% '022f", 1615 | "%+'022f", 1616 | "%-'22f", 1617 | "%'22f", 1618 | #if HAVE_LONG_LONG_INT 1619 | "%.16f", 1620 | "%22.16f", 1621 | "%022.16f", 1622 | "%-22.16f", 1623 | "%#+'022.16f", 1624 | #endif /* HAVE_LONG_LONG_INT */ 1625 | #endif /* !OS_BSD && !OS_IRIX */ 1626 | "foo|%#+0123.9F|bar", 1627 | "%-123.9f", 1628 | "%123.9f", 1629 | "%+23.9f", 1630 | "%+#010.0f", 1631 | "%#10.1f", 1632 | "%10.5f", 1633 | "% 10.5f", 1634 | "%+05.8f", 1635 | "%-05.8f", 1636 | "%05.8f", 1637 | "%+5.8f", 1638 | "%-5.8f", 1639 | "% 5.8f", 1640 | "%5.8f", 1641 | "%5.0f", 1642 | "%5.f", 1643 | "%#5.0f", 1644 | "%#5.f", 1645 | "%+4.9f", 1646 | "%3.2f", 1647 | "%3.1f", 1648 | "%-1.5f", 1649 | "%1.5f", 1650 | "%01.3f", 1651 | "%1.f", 1652 | "%.1f", 1653 | "%#.0f", 1654 | "%+.0f", 1655 | "% .0f", 1656 | "%.0f", 1657 | "%#.f", 1658 | "%+.f", 1659 | "% .f", 1660 | "%.f", 1661 | "%4f", 1662 | "%f", 1663 | "%F", 1664 | /* "%G" and "%g" formats. */ 1665 | #if !OS_BSD && !OS_IRIX && !OS_LINUX 1666 | "% '022g", 1667 | "%+'022g", 1668 | "%-'22g", 1669 | "%'22g", 1670 | #if HAVE_LONG_LONG_INT 1671 | "%.16g", 1672 | "%22.16g", 1673 | "%022.16g", 1674 | "%-22.16g", 1675 | "%#+'022.16g", 1676 | #endif /* HAVE_LONG_LONG_INT */ 1677 | #endif /* !OS_BSD && !OS_IRIX && !OS_LINUX */ 1678 | "foo|%#+0123.9G|bar", 1679 | "%-123.9g", 1680 | "%123.9g", 1681 | "%+23.9g", 1682 | "%+05.8g", 1683 | "%-05.8g", 1684 | "%05.8g", 1685 | "%+5.8g", 1686 | "%-5.8g", 1687 | "% 5.8g", 1688 | "%5.8g", 1689 | "%+4.9g", 1690 | #if !OS_LINUX /* glibc sometimes gets these wrong. */ 1691 | "%+#010.0g", 1692 | "%#10.1g", 1693 | "%10.5g", 1694 | "% 10.5g", 1695 | "%5.0g", 1696 | "%5.g", 1697 | "%#5.0g", 1698 | "%#5.g", 1699 | "%3.2g", 1700 | "%3.1g", 1701 | "%-1.5g", 1702 | "%1.5g", 1703 | "%01.3g", 1704 | "%1.g", 1705 | "%.1g", 1706 | "%#.0g", 1707 | "%+.0g", 1708 | "% .0g", 1709 | "%.0g", 1710 | "%#.g", 1711 | "%+.g", 1712 | "% .g", 1713 | "%.g", 1714 | "%4g", 1715 | "%g", 1716 | "%G", 1717 | #endif /* !OS_LINUX */ 1718 | NULL 1719 | }; 1720 | double float_val[] = { 1721 | -4.136, 1722 | -134.52, 1723 | -5.04030201, 1724 | -3410.01234, 1725 | -999999.999999, 1726 | -913450.29876, 1727 | -913450.2, 1728 | -91345.2, 1729 | -9134.2, 1730 | -913.2, 1731 | -91.2, 1732 | -9.2, 1733 | -9.9, 1734 | 4.136, 1735 | 134.52, 1736 | 5.04030201, 1737 | 3410.01234, 1738 | 999999.999999, 1739 | 913450.29876, 1740 | 913450.2, 1741 | 91345.2, 1742 | 9134.2, 1743 | 913.2, 1744 | 91.2, 1745 | 9.2, 1746 | 9.9, 1747 | 9.96, 1748 | 9.996, 1749 | 9.9996, 1750 | 9.99996, 1751 | 9.999996, 1752 | 9.9999996, 1753 | 9.99999996, 1754 | 0.99999996, 1755 | 0.99999999, 1756 | 0.09999999, 1757 | 0.00999999, 1758 | 0.00099999, 1759 | 0.00009999, 1760 | 0.00000999, 1761 | 0.00000099, 1762 | 0.00000009, 1763 | 0.00000001, 1764 | 0.0000001, 1765 | 0.000001, 1766 | 0.00001, 1767 | 0.0001, 1768 | 0.001, 1769 | 0.01, 1770 | 0.1, 1771 | 1.0, 1772 | 1.5, 1773 | -1.5, 1774 | -1.0, 1775 | -0.1, 1776 | #if !OS_BSD /* BSD sometimes gets these wrong. */ 1777 | #ifdef INFINITY 1778 | INFINITY, 1779 | -INFINITY, 1780 | #endif /* defined(INFINITY) */ 1781 | #ifdef NAN 1782 | NAN, 1783 | #endif /* defined(NAN) */ 1784 | #endif /* !OS_BSD */ 1785 | 0 1786 | }; 1787 | const char *long_fmt[] = { 1788 | "foo|%0123ld|bar", 1789 | #if !OS_IRIX 1790 | "% '0123ld", 1791 | "%+'0123ld", 1792 | "%-'123ld", 1793 | "%'123ld", 1794 | #endif /* !OS_IRiX */ 1795 | "%123.9ld", 1796 | "% 123.9ld", 1797 | "%+123.9ld", 1798 | "%-123.9ld", 1799 | "%0123ld", 1800 | "% 0123ld", 1801 | "%+0123ld", 1802 | "%-0123ld", 1803 | "%10.5ld", 1804 | "% 10.5ld", 1805 | "%+10.5ld", 1806 | "%-10.5ld", 1807 | "%010ld", 1808 | "% 010ld", 1809 | "%+010ld", 1810 | "%-010ld", 1811 | "%4.2ld", 1812 | "% 4.2ld", 1813 | "%+4.2ld", 1814 | "%-4.2ld", 1815 | "%04ld", 1816 | "% 04ld", 1817 | "%+04ld", 1818 | "%-04ld", 1819 | "%5.5ld", 1820 | "%+22.33ld", 1821 | "%01.3ld", 1822 | "%1.5ld", 1823 | "%-1.5ld", 1824 | "%44ld", 1825 | "%4ld", 1826 | "%4.0ld", 1827 | "%4.ld", 1828 | "%.44ld", 1829 | "%.4ld", 1830 | "%.0ld", 1831 | "%.ld", 1832 | "%ld", 1833 | NULL 1834 | }; 1835 | long int long_val[] = { 1836 | #ifdef LONG_MAX 1837 | LONG_MAX, 1838 | #endif /* LONG_MAX */ 1839 | #ifdef LONG_MIN 1840 | LONG_MIN, 1841 | #endif /* LONG_MIN */ 1842 | -91340, 1843 | 91340, 1844 | 341, 1845 | 134, 1846 | 0203, 1847 | -1, 1848 | 1, 1849 | 0 1850 | }; 1851 | const char *ulong_fmt[] = { 1852 | /* "%u" formats. */ 1853 | "foo|%0123lu|bar", 1854 | #if !OS_IRIX 1855 | "% '0123lu", 1856 | "%+'0123lu", 1857 | "%-'123lu", 1858 | "%'123lu", 1859 | #endif /* !OS_IRiX */ 1860 | "%123.9lu", 1861 | "% 123.9lu", 1862 | "%+123.9lu", 1863 | "%-123.9lu", 1864 | "%0123lu", 1865 | "% 0123lu", 1866 | "%+0123lu", 1867 | "%-0123lu", 1868 | "%5.5lu", 1869 | "%+22.33lu", 1870 | "%01.3lu", 1871 | "%1.5lu", 1872 | "%-1.5lu", 1873 | "%44lu", 1874 | "%lu", 1875 | /* "%o" formats. */ 1876 | "foo|%#0123lo|bar", 1877 | "%#123.9lo", 1878 | "%# 123.9lo", 1879 | "%#+123.9lo", 1880 | "%#-123.9lo", 1881 | "%#0123lo", 1882 | "%# 0123lo", 1883 | "%#+0123lo", 1884 | "%#-0123lo", 1885 | "%#5.5lo", 1886 | "%#+22.33lo", 1887 | "%#01.3lo", 1888 | "%#1.5lo", 1889 | "%#-1.5lo", 1890 | "%#44lo", 1891 | "%#lo", 1892 | "%123.9lo", 1893 | "% 123.9lo", 1894 | "%+123.9lo", 1895 | "%-123.9lo", 1896 | "%0123lo", 1897 | "% 0123lo", 1898 | "%+0123lo", 1899 | "%-0123lo", 1900 | "%5.5lo", 1901 | "%+22.33lo", 1902 | "%01.3lo", 1903 | "%1.5lo", 1904 | "%-1.5lo", 1905 | "%44lo", 1906 | "%lo", 1907 | /* "%X" and "%x" formats. */ 1908 | "foo|%#0123lX|bar", 1909 | "%#123.9lx", 1910 | "%# 123.9lx", 1911 | "%#+123.9lx", 1912 | "%#-123.9lx", 1913 | "%#0123lx", 1914 | "%# 0123lx", 1915 | "%#+0123lx", 1916 | "%#-0123lx", 1917 | "%#5.5lx", 1918 | "%#+22.33lx", 1919 | "%#01.3lx", 1920 | "%#1.5lx", 1921 | "%#-1.5lx", 1922 | "%#44lx", 1923 | "%#lx", 1924 | "%#lX", 1925 | "%123.9lx", 1926 | "% 123.9lx", 1927 | "%+123.9lx", 1928 | "%-123.9lx", 1929 | "%0123lx", 1930 | "% 0123lx", 1931 | "%+0123lx", 1932 | "%-0123lx", 1933 | "%5.5lx", 1934 | "%+22.33lx", 1935 | "%01.3lx", 1936 | "%1.5lx", 1937 | "%-1.5lx", 1938 | "%44lx", 1939 | "%lx", 1940 | "%lX", 1941 | NULL 1942 | }; 1943 | unsigned long int ulong_val[] = { 1944 | #ifdef ULONG_MAX 1945 | ULONG_MAX, 1946 | #endif /* ULONG_MAX */ 1947 | 91340, 1948 | 341, 1949 | 134, 1950 | 0203, 1951 | 1, 1952 | 0 1953 | }; 1954 | const char *llong_fmt[] = { 1955 | "foo|%0123lld|bar", 1956 | "%123.9lld", 1957 | "% 123.9lld", 1958 | "%+123.9lld", 1959 | "%-123.9lld", 1960 | "%0123lld", 1961 | "% 0123lld", 1962 | "%+0123lld", 1963 | "%-0123lld", 1964 | "%5.5lld", 1965 | "%+22.33lld", 1966 | "%01.3lld", 1967 | "%1.5lld", 1968 | "%-1.5lld", 1969 | "%44lld", 1970 | "%lld", 1971 | NULL 1972 | }; 1973 | LLONG llong_val[] = { 1974 | #ifdef LLONG_MAX 1975 | LLONG_MAX, 1976 | #endif /* LLONG_MAX */ 1977 | #ifdef LLONG_MIN 1978 | LLONG_MIN, 1979 | #endif /* LLONG_MIN */ 1980 | -91340, 1981 | 91340, 1982 | 341, 1983 | 134, 1984 | 0203, 1985 | -1, 1986 | 1, 1987 | 0 1988 | }; 1989 | const char *string_fmt[] = { 1990 | "foo|%10.10s|bar", 1991 | "%-10.10s", 1992 | "%10.10s", 1993 | "%10.5s", 1994 | "%5.10s", 1995 | "%10.1s", 1996 | "%1.10s", 1997 | "%10.0s", 1998 | "%0.10s", 1999 | "%-42.5s", 2000 | "%2.s", 2001 | "%.10s", 2002 | "%.1s", 2003 | "%.0s", 2004 | "%.s", 2005 | "%4s", 2006 | "%s", 2007 | NULL 2008 | }; 2009 | const char *string_val[] = { 2010 | "Hello", 2011 | "Hello, world!", 2012 | "Sound check: One, two, three.", 2013 | "This string is a little longer than the other strings.", 2014 | "1", 2015 | "", 2016 | NULL 2017 | }; 2018 | #if !OS_SYSV /* SysV uses a different format than we do. */ 2019 | const char *pointer_fmt[] = { 2020 | "foo|%p|bar", 2021 | "%42p", 2022 | "%p", 2023 | NULL 2024 | }; 2025 | const char *pointer_val[] = { 2026 | *pointer_fmt, 2027 | *string_fmt, 2028 | *string_val, 2029 | NULL 2030 | }; 2031 | #endif /* !OS_SYSV */ 2032 | char buf1[1024], buf2[1024]; 2033 | double value, digits = 9.123456789012345678901234567890123456789; 2034 | int i, j, r1, r2, failed = 0, num = 0; 2035 | 2036 | /* 2037 | * Use -DTEST_NILS in order to also test the conversion of nil values. Might 2038 | * segfault on systems which don't support converting a NULL pointer with "%s" 2039 | * and lets some test cases fail against BSD and glibc due to bugs in their 2040 | * implementations. 2041 | */ 2042 | #ifndef TEST_NILS 2043 | #define TEST_NILS 0 2044 | #elif TEST_NILS 2045 | #undef TEST_NILS 2046 | #define TEST_NILS 1 2047 | #endif /* !defined(TEST_NILS) */ 2048 | #ifdef TEST 2049 | #undef TEST 2050 | #endif /* defined(TEST) */ 2051 | #define TEST(fmt, val) \ 2052 | do { \ 2053 | for (i = 0; fmt[i] != NULL; i++) \ 2054 | for (j = 0; j == 0 || val[j - TEST_NILS] != 0; j++) { \ 2055 | r1 = sprintf(buf1, fmt[i], val[j]); \ 2056 | r2 = snprintf(buf2, sizeof(buf2), fmt[i], val[j]); \ 2057 | if (strcmp(buf1, buf2) != 0 || r1 != r2) { \ 2058 | (void)printf("Results don't match, " \ 2059 | "format string: %s\n" \ 2060 | "\t sprintf(3): [%s] (%d)\n" \ 2061 | "\tsnprintf(3): [%s] (%d)\n", \ 2062 | fmt[i], buf1, r1, buf2, r2); \ 2063 | failed++; \ 2064 | } \ 2065 | num++; \ 2066 | } \ 2067 | } while (/* CONSTCOND */ 0) 2068 | 2069 | #if HAVE_LOCALE_H 2070 | (void)setlocale(LC_ALL, ""); 2071 | #endif /* HAVE_LOCALE_H */ 2072 | 2073 | (void)puts("Testing our snprintf(3) against your system's sprintf(3)."); 2074 | TEST(float_fmt, float_val); 2075 | TEST(long_fmt, long_val); 2076 | TEST(ulong_fmt, ulong_val); 2077 | TEST(llong_fmt, llong_val); 2078 | TEST(string_fmt, string_val); 2079 | #if !OS_SYSV /* SysV uses a different format than we do. */ 2080 | TEST(pointer_fmt, pointer_val); 2081 | #endif /* !OS_SYSV */ 2082 | (void)printf("Result: %d out of %d tests failed.\n", failed, num); 2083 | 2084 | (void)fputs("Checking how many digits we support: ", stdout); 2085 | for (i = 0; i < 100; i++) { 2086 | value = pow(10, i) * digits; 2087 | (void)sprintf(buf1, "%.1f", value); 2088 | (void)snprintf(buf2, sizeof(buf2), "%.1f", value); 2089 | if (strcmp(buf1, buf2) != 0) { 2090 | (void)printf("apparently %d.\n", i); 2091 | break; 2092 | } 2093 | } 2094 | return (failed == 0) ? 0 : 1; 2095 | } 2096 | #endif /* TEST_SNPRINTF */ 2097 | 2098 | /* vim: set joinspaces textwidth=80: */ 2099 | 2100 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | /*-test 2 | /*.o 3 | -------------------------------------------------------------------------------- /test/autohelp-1.out: -------------------------------------------------------------------------------- 1 | args: «--help» «--» «--is-not-passed» «ignoreme» 2 | 3 | usage: autohelp-test-case [opts...] [--] [extras...] 4 | 5 | Tests the simple parser macro 6 | 7 | -i, --some-int=n Some integer value. Can set to whatever number you like. 8 | -f Some float value. 9 | --some-double=n Some double value. 10 | -?, --help Shows this help message 11 | 12 | [end of arguments] 13 | -------------------------------------------------------------------------------- /test/autohelp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../xopt.h" 6 | 7 | typedef struct { 8 | int someInt; 9 | float someFloat; 10 | double someDouble; 11 | bool help; 12 | } SimpleConfig; 13 | 14 | xoptOption options[] = { 15 | { 16 | "some-int", 17 | 'i', 18 | offsetof(SimpleConfig, someInt), 19 | 0, 20 | XOPT_TYPE_INT, 21 | "n", 22 | "Some integer value. Can set to whatever number you like." 23 | }, 24 | { 25 | 0, 26 | 'f', 27 | offsetof(SimpleConfig, someFloat), 28 | 0, 29 | XOPT_TYPE_FLOAT, 30 | "n", 31 | "Some float value." 32 | }, 33 | { 34 | "some-double", 35 | 0, 36 | offsetof(SimpleConfig, someDouble), 37 | 0, 38 | XOPT_TYPE_DOUBLE, 39 | "n", 40 | "Some double value." 41 | }, 42 | { 43 | "help", 44 | '?', 45 | offsetof(SimpleConfig, help), 46 | 0, 47 | XOPT_TYPE_BOOL, 48 | 0, 49 | "Shows this help message" 50 | }, 51 | XOPT_NULLOPTION 52 | }; 53 | 54 | int main(int argc, const char **argv) { 55 | int exit_code = 1; 56 | const char *err = NULL; 57 | SimpleConfig config; 58 | const char **extras = NULL; 59 | int extraCount = 0; 60 | 61 | /* show arguments */ 62 | fputs("args:", stderr); 63 | for (int i = 1; i < argc; i++) { 64 | fprintf(stderr, " «%s»", argv[i]); 65 | } 66 | fputs("\n\n", stderr); 67 | 68 | /* setup defaults */ 69 | config.someInt = 0; 70 | config.someDouble = 0.0; 71 | config.help = 0; 72 | 73 | XOPT_SIMPLE_PARSE( 74 | "autohelp-test-case", 75 | 0, 76 | &options[0], &config, 77 | argc, argv, 78 | &extraCount, &extras, 79 | &err, 80 | stderr, 81 | "[opts...] [--] [extras...]", 82 | "Tests the simple parser macro", 83 | "[end of arguments]", 84 | 15); 85 | 86 | if (!err) { 87 | err = config.help 88 | ? "--help was passed but autohelp didn't fire" 89 | : "--help was not passed - it is required for this test case"; 90 | } 91 | 92 | fprintf(stderr, "Error: %s\n", err); 93 | 94 | exit: 95 | free(extras); /* DO NOT free individual strings */ 96 | return exit_code; 97 | xopt_help: 98 | exit_code = 0; 99 | goto exit; 100 | } 101 | -------------------------------------------------------------------------------- /test/macro-1.out: -------------------------------------------------------------------------------- 1 | args: «--some-int=10» «--some-double=14.5» «foo» «bar» «--» «--some-other=20» 2 | 3 | someInt: 10 4 | someFloat: 0.000000 5 | someDouble: 14.500000 6 | help: 0 7 | 8 | extra count: 3 9 | - foo 10 | - bar 11 | - --some-other=20 12 | -------------------------------------------------------------------------------- /test/macro.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../xopt.h" 6 | 7 | typedef struct { 8 | int someInt; 9 | float someFloat; 10 | double someDouble; 11 | bool help; 12 | } SimpleConfig; 13 | 14 | xoptOption options[] = { 15 | { 16 | "some-int", 17 | 'i', 18 | offsetof(SimpleConfig, someInt), 19 | 0, 20 | XOPT_TYPE_INT, 21 | "n", 22 | "Some integer value. Can set to whatever number you like." 23 | }, 24 | { 25 | "some-float", 26 | 'f', 27 | offsetof(SimpleConfig, someFloat), 28 | 0, 29 | XOPT_TYPE_FLOAT, 30 | "n", 31 | "Some float value." 32 | }, 33 | { 34 | "some-double", 35 | 'd', 36 | offsetof(SimpleConfig, someDouble), 37 | 0, 38 | XOPT_TYPE_DOUBLE, 39 | "n", 40 | "Some double value." 41 | }, 42 | { 43 | "help", 44 | '?', 45 | offsetof(SimpleConfig, help), 46 | 0, 47 | XOPT_TYPE_BOOL, 48 | 0, 49 | "Shows this help message" 50 | }, 51 | XOPT_NULLOPTION 52 | }; 53 | 54 | int main(int argc, const char **argv) { 55 | int exit_code = 1; 56 | const char *err = NULL; 57 | SimpleConfig config; 58 | const char **extras = NULL; 59 | const char **extrasPtr = NULL; 60 | int extraCount = 0; 61 | 62 | /* show arguments */ 63 | fputs("args:", stderr); 64 | for (int i = 1; i < argc; i++) { 65 | fprintf(stderr, " «%s»", argv[i]); 66 | } 67 | fputs("\n\n", stderr); 68 | 69 | /* setup defaults */ 70 | config.someInt = 0; 71 | config.someDouble = 0.0; 72 | config.help = 0; 73 | 74 | XOPT_SIMPLE_PARSE( 75 | argv[0], 76 | 0, 77 | &options[0], &config, 78 | argc, argv, 79 | &extraCount, &extras, 80 | &err, 81 | stderr, 82 | "[opts...] [--] [extras...]", 83 | "Tests the simple parser macro", 84 | "[end of arguments]", 85 | 15); 86 | 87 | if (err) { 88 | fprintf(stderr, "Error: %s\n", err); 89 | goto exit; 90 | } 91 | 92 | /* print */ 93 | #define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \ 94 | config.field) 95 | 96 | P(someInt, d); 97 | P(someFloat, f); 98 | P(someDouble, f); 99 | P(help, d); 100 | 101 | fprintf(stderr, "\nextra count: %d\n", extraCount); 102 | extrasPtr = extras; 103 | while (extraCount--) { 104 | fprintf(stderr, "- %s\n", *extrasPtr++); 105 | } 106 | 107 | #undef P 108 | 109 | exit_code = 0; 110 | exit: 111 | free(extras); /* DO NOT free individual strings */ 112 | return exit_code; 113 | xopt_help: 114 | exit_code = 2; 115 | goto exit; 116 | } 117 | -------------------------------------------------------------------------------- /test/nocondense-1.out: -------------------------------------------------------------------------------- 1 | args: «-i» «10» «-d» «14.5» «-ssome string» «-m» «-mm» «-mmm» «foo» «bar» «--» «--is-not-passed» «ignoreme» 2 | 3 | Error: short option parameters must be separated, not condensed: -ssome string 4 | -------------------------------------------------------------------------------- /test/nocondense-sloppy-1.out: -------------------------------------------------------------------------------- 1 | args: «-i» «10» «-d» «14.5» «-s» «some string» «-m» «-mm» «-mmm» «foo» «bar» «--» «--is-not-passed» «ignoreme» 2 | 3 | Error: short options cannot be combined: -mm 4 | -------------------------------------------------------------------------------- /test/nocondense-sloppy-2.out: -------------------------------------------------------------------------------- 1 | args: «-i» «10» «-d» «14.5» «-ssome string» «-m» «-mm» «-mmm» «foo» «bar» «--» «--is-not-passed» «ignoreme» 2 | 3 | Error: short options cannot be combined: -mm 4 | -------------------------------------------------------------------------------- /test/nocondense-sloppy-3.out: -------------------------------------------------------------------------------- 1 | args: «-i» «10» «-d» «14.5» «-ssome string» «-m» «-m» «-m» «-m» «-m» «-m» «foo» «bar» «--» «--is-not-passed» «ignoreme» 2 | 3 | someInt: 10 4 | someFloat: 0.000000 5 | someDouble: 14.500000 6 | someString: some string 7 | multiCount: 6 8 | help: 0 9 | 10 | extra count: 4 11 | - foo 12 | - bar 13 | - --is-not-passed 14 | - ignoreme 15 | -------------------------------------------------------------------------------- /test/nocondense-sloppy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../xopt.h" 6 | 7 | typedef struct { 8 | int someInt; 9 | float someFloat; 10 | double someDouble; 11 | const char *someString; 12 | int multiCount; 13 | bool help; 14 | } SimpleConfig; 15 | 16 | static void on_verbose(const char *v, void *data, const struct xoptOption *option, bool longArg, const char **err) { 17 | (void) v; 18 | (void) longArg; 19 | (void) err; 20 | int *count = (int *)(((char *) data) + option->offset); 21 | ++*count; 22 | } 23 | 24 | xoptOption options[] = { 25 | { 26 | "some-int", 27 | 'i', 28 | offsetof(SimpleConfig, someInt), 29 | 0, 30 | XOPT_TYPE_INT, 31 | "n", 32 | "Some integer value. Can set to whatever number you like." 33 | }, 34 | { 35 | "some-float", 36 | 'f', 37 | offsetof(SimpleConfig, someFloat), 38 | 0, 39 | XOPT_TYPE_FLOAT, 40 | "n", 41 | "Some float value." 42 | }, 43 | { 44 | "some-double", 45 | 'd', 46 | offsetof(SimpleConfig, someDouble), 47 | 0, 48 | XOPT_TYPE_DOUBLE, 49 | "n", 50 | "Some double value." 51 | }, 52 | { 53 | "some-string", 54 | 's', 55 | offsetof(SimpleConfig, someString), 56 | 0, 57 | XOPT_TYPE_STRING, 58 | "s", 59 | "Some string value." 60 | }, 61 | { 62 | 0, 63 | 'm', 64 | offsetof(SimpleConfig, multiCount), 65 | &on_verbose, 66 | XOPT_TYPE_BOOL, 67 | 0, 68 | "Specify multiple times to increase count" 69 | }, 70 | { 71 | "help", 72 | '?', 73 | offsetof(SimpleConfig, help), 74 | 0, 75 | XOPT_TYPE_BOOL, 76 | 0, 77 | "Shows this help message" 78 | }, 79 | XOPT_NULLOPTION 80 | }; 81 | 82 | int main(int argc, const char **argv) { 83 | int exit_code = 1; 84 | const char *err = NULL; 85 | SimpleConfig config; 86 | const char **extras = NULL; 87 | const char **extrasPtr = NULL; 88 | int extraCount = 0; 89 | 90 | /* show arguments */ 91 | fputs("args:", stderr); 92 | for (int i = 1; i < argc; i++) { 93 | fprintf(stderr, " «%s»", argv[i]); 94 | } 95 | fputs("\n\n", stderr); 96 | 97 | /* setup defaults */ 98 | config.someInt = 0; 99 | config.someDouble = 0.0; 100 | config.someFloat = 0.0f; 101 | config.someString = 0; 102 | config.multiCount = 0; 103 | config.help = 0; 104 | 105 | XOPT_SIMPLE_PARSE( 106 | argv[0], 107 | XOPT_CTX_SLOPPYSHORTS | XOPT_CTX_NOCONDENSE, 108 | &options[0], &config, 109 | argc, argv, 110 | &extraCount, &extras, 111 | &err, 112 | stderr, 113 | "[opts...] [--] [extras...]", 114 | "Tests the simple parser macro", 115 | "[end of arguments]", 116 | 15); 117 | 118 | if (err) { 119 | fprintf(stderr, "Error: %s\n", err); 120 | goto exit; 121 | } 122 | 123 | /* print */ 124 | #define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \ 125 | config.field) 126 | 127 | P(someInt, d); 128 | P(someFloat, f); 129 | P(someDouble, f); 130 | P(someString, s); 131 | P(multiCount, d); 132 | P(help, d); 133 | 134 | fprintf(stderr, "\nextra count: %d\n", extraCount); 135 | extrasPtr = extras; 136 | while (extraCount--) { 137 | fprintf(stderr, "- %s\n", *extrasPtr++); 138 | } 139 | 140 | #undef P 141 | 142 | exit_code = 0; 143 | exit: 144 | free(extras); /* DO NOT free individual strings */ 145 | return exit_code; 146 | xopt_help: 147 | exit_code = 2; 148 | goto exit; 149 | } 150 | -------------------------------------------------------------------------------- /test/nocondense.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../xopt.h" 6 | 7 | typedef struct { 8 | int someInt; 9 | float someFloat; 10 | double someDouble; 11 | const char *someString; 12 | int multiCount; 13 | bool help; 14 | } SimpleConfig; 15 | 16 | static void on_verbose(const char *v, void *data, const struct xoptOption *option, bool longArg, const char **err) { 17 | (void) v; 18 | (void) longArg; 19 | (void) err; 20 | int *count = (int *)(((char *) data) + option->offset); 21 | ++*count; 22 | } 23 | 24 | xoptOption options[] = { 25 | { 26 | "some-int", 27 | 'i', 28 | offsetof(SimpleConfig, someInt), 29 | 0, 30 | XOPT_TYPE_INT, 31 | "n", 32 | "Some integer value. Can set to whatever number you like." 33 | }, 34 | { 35 | "some-float", 36 | 'f', 37 | offsetof(SimpleConfig, someFloat), 38 | 0, 39 | XOPT_TYPE_FLOAT, 40 | "n", 41 | "Some float value." 42 | }, 43 | { 44 | "some-double", 45 | 'd', 46 | offsetof(SimpleConfig, someDouble), 47 | 0, 48 | XOPT_TYPE_DOUBLE, 49 | "n", 50 | "Some double value." 51 | }, 52 | { 53 | "some-string", 54 | 's', 55 | offsetof(SimpleConfig, someString), 56 | 0, 57 | XOPT_TYPE_STRING, 58 | "s", 59 | "Some string value." 60 | }, 61 | { 62 | 0, 63 | 'm', 64 | offsetof(SimpleConfig, multiCount), 65 | &on_verbose, 66 | XOPT_TYPE_BOOL, 67 | 0, 68 | "Specify multiple times to increase count" 69 | }, 70 | { 71 | "help", 72 | '?', 73 | offsetof(SimpleConfig, help), 74 | 0, 75 | XOPT_TYPE_BOOL, 76 | 0, 77 | "Shows this help message" 78 | }, 79 | XOPT_NULLOPTION 80 | }; 81 | 82 | int main(int argc, const char **argv) { 83 | int exit_code = 1; 84 | const char *err = NULL; 85 | SimpleConfig config; 86 | const char **extras = NULL; 87 | const char **extrasPtr = NULL; 88 | int extraCount = 0; 89 | 90 | /* show arguments */ 91 | fputs("args:", stderr); 92 | for (int i = 1; i < argc; i++) { 93 | fprintf(stderr, " «%s»", argv[i]); 94 | } 95 | fputs("\n\n", stderr); 96 | 97 | /* setup defaults */ 98 | config.someInt = 0; 99 | config.someDouble = 0.0; 100 | config.someFloat = 0.0f; 101 | config.someString = 0; 102 | config.multiCount = 0; 103 | config.help = 0; 104 | 105 | XOPT_SIMPLE_PARSE( 106 | argv[0], 107 | XOPT_CTX_NOCONDENSE, 108 | &options[0], &config, 109 | argc, argv, 110 | &extraCount, &extras, 111 | &err, 112 | stderr, 113 | "[opts...] [--] [extras...]", 114 | "Tests the simple parser macro", 115 | "[end of arguments]", 116 | 15); 117 | 118 | if (err) { 119 | fprintf(stderr, "Error: %s\n", err); 120 | goto exit; 121 | } 122 | 123 | /* print */ 124 | #define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \ 125 | config.field) 126 | 127 | P(someInt, d); 128 | P(someFloat, f); 129 | P(someDouble, f); 130 | P(someString, s); 131 | P(multiCount, d); 132 | P(help, d); 133 | 134 | fprintf(stderr, "\nextra count: %d\n", extraCount); 135 | extrasPtr = extras; 136 | while (extraCount--) { 137 | fprintf(stderr, "- %s\n", *extrasPtr++); 138 | } 139 | 140 | #undef P 141 | 142 | exit_code = 0; 143 | exit: 144 | free(extras); /* DO NOT free individual strings */ 145 | return exit_code; 146 | xopt_help: 147 | exit_code = 2; 148 | goto exit; 149 | } 150 | -------------------------------------------------------------------------------- /test/optional-longarg-1.out: -------------------------------------------------------------------------------- 1 | args: «-i» «10» «-d» «14.5» «foo» «bar» «--» «--some-other=20» 2 | 3 | someInt: 10 4 | someFloat: 0.000000 5 | someDouble: 14.500000 6 | help: 0 7 | 8 | extra count: 3 9 | - foo 10 | - bar 11 | - --some-other=20 12 | -------------------------------------------------------------------------------- /test/optional-longarg.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../xopt.h" 6 | 7 | typedef struct { 8 | int someInt; 9 | float someFloat; 10 | double someDouble; 11 | bool help; 12 | } SimpleConfig; 13 | 14 | xoptOption options[] = { 15 | { 16 | 0, 17 | 'i', 18 | offsetof(SimpleConfig, someInt), 19 | 0, 20 | XOPT_TYPE_INT, 21 | "n", 22 | "Some integer value. Can set to whatever number you like." 23 | }, 24 | { 25 | 0, 26 | 'f', 27 | offsetof(SimpleConfig, someFloat), 28 | 0, 29 | XOPT_TYPE_FLOAT, 30 | "n", 31 | "Some float value." 32 | }, 33 | { 34 | 0, 35 | 'd', 36 | offsetof(SimpleConfig, someDouble), 37 | 0, 38 | XOPT_TYPE_DOUBLE, 39 | "n", 40 | "Some double value." 41 | }, 42 | { 43 | "help", 44 | '?', 45 | offsetof(SimpleConfig, help), 46 | 0, 47 | XOPT_TYPE_BOOL, 48 | 0, 49 | "Shows this help message" 50 | }, 51 | XOPT_NULLOPTION 52 | }; 53 | 54 | int main(int argc, const char **argv) { 55 | int exit_code = 1; 56 | const char *err = NULL; 57 | SimpleConfig config; 58 | const char **extras = NULL; 59 | const char **extrasPtr = NULL; 60 | int extraCount = 0; 61 | 62 | /* show arguments */ 63 | fputs("args:", stderr); 64 | for (int i = 1; i < argc; i++) { 65 | fprintf(stderr, " «%s»", argv[i]); 66 | } 67 | fputs("\n\n", stderr); 68 | 69 | /* setup defaults */ 70 | config.someInt = 0; 71 | config.someDouble = 0.0; 72 | config.help = 0; 73 | 74 | XOPT_SIMPLE_PARSE( 75 | argv[0], 76 | 0, 77 | &options[0], &config, 78 | argc, argv, 79 | &extraCount, &extras, 80 | &err, 81 | stderr, 82 | "macro-test [opts...] [--] [extras...]", 83 | "Tests the simple parser macro", 84 | "[end of arguments]", 85 | 15); 86 | 87 | if (err) { 88 | fprintf(stderr, "Error: %s\n", err); 89 | goto exit; 90 | } 91 | 92 | /* print */ 93 | #define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \ 94 | config.field) 95 | 96 | P(someInt, d); 97 | P(someFloat, f); 98 | P(someDouble, f); 99 | P(help, d); 100 | 101 | fprintf(stderr, "\nextra count: %d\n", extraCount); 102 | extrasPtr = extras; 103 | while (extraCount--) { 104 | fprintf(stderr, "- %s\n", *extrasPtr++); 105 | } 106 | 107 | #undef P 108 | 109 | exit_code = 0; 110 | exit: 111 | free(extras); /* DO NOT free individual strings */ 112 | return exit_code; 113 | xopt_help: 114 | exit_code = 2; 115 | goto exit; 116 | } 117 | -------------------------------------------------------------------------------- /test/required-1.out: -------------------------------------------------------------------------------- 1 | args: «--some-int=10» «--some-double=14.5» «--some-required=1337» «foo» «bar» «--» «--some-other=20» 2 | 3 | someInt: 10 4 | someFloat: 0.000000 5 | someDouble: 14.500000 6 | someRequired: 1337 7 | help: 0 8 | 9 | extra count: 3 10 | - foo 11 | - bar 12 | - --some-other=20 13 | -------------------------------------------------------------------------------- /test/required.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../xopt.h" 6 | 7 | typedef struct { 8 | int someInt; 9 | float someFloat; 10 | double someDouble; 11 | int someRequired; 12 | bool help; 13 | } SimpleConfig; 14 | 15 | xoptOption options[] = { 16 | { 17 | "some-int", 18 | 'i', 19 | offsetof(SimpleConfig, someInt), 20 | 0, 21 | XOPT_TYPE_INT, 22 | "n", 23 | "Some integer value. Can set to whatever number you like." 24 | }, 25 | { 26 | "some-float", 27 | 'f', 28 | offsetof(SimpleConfig, someFloat), 29 | 0, 30 | XOPT_TYPE_FLOAT, 31 | "n", 32 | "Some float value." 33 | }, 34 | { 35 | "some-double", 36 | 'd', 37 | offsetof(SimpleConfig, someDouble), 38 | 0, 39 | XOPT_TYPE_DOUBLE, 40 | "n", 41 | "Some double value." 42 | }, 43 | { 44 | "some-required", 45 | 'r', 46 | offsetof(SimpleConfig, someRequired), 47 | 0, 48 | XOPT_TYPE_INT | XOPT_REQUIRED, 49 | "n", 50 | "Some value" 51 | }, 52 | { 53 | "help", 54 | '?', 55 | offsetof(SimpleConfig, help), 56 | 0, 57 | XOPT_TYPE_BOOL, 58 | 0, 59 | "Shows this help message" 60 | }, 61 | XOPT_NULLOPTION 62 | }; 63 | 64 | int main(int argc, const char **argv) { 65 | int exit_code = 1; 66 | const char *err = NULL; 67 | SimpleConfig config; 68 | const char **extras = NULL; 69 | const char **extrasPtr = NULL; 70 | int extraCount = 0; 71 | 72 | /* show arguments */ 73 | fputs("args:", stderr); 74 | for (int i = 1; i < argc; i++) { 75 | fprintf(stderr, " «%s»", argv[i]); 76 | } 77 | fputs("\n\n", stderr); 78 | 79 | /* setup defaults */ 80 | config.someInt = 0; 81 | config.someDouble = 0.0; 82 | config.someRequired = 0; 83 | config.help = 0; 84 | 85 | XOPT_SIMPLE_PARSE( 86 | argv[0], 87 | 0, 88 | &options[0], &config, 89 | argc, argv, 90 | &extraCount, &extras, 91 | &err, 92 | stderr, 93 | "[opts...] [--] [extras...]", 94 | "Tests the simple parser macro", 95 | "[end of arguments]", 96 | 15); 97 | 98 | if (err) { 99 | fprintf(stderr, "Error: %s\n", err); 100 | goto exit; 101 | } 102 | 103 | /* print */ 104 | #define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \ 105 | config.field) 106 | 107 | P(someInt, d); 108 | P(someFloat, f); 109 | P(someDouble, f); 110 | P(someRequired, d); 111 | P(help, d); 112 | 113 | fprintf(stderr, "\nextra count: %d\n", extraCount); 114 | extrasPtr = extras; 115 | while (extraCount--) { 116 | fprintf(stderr, "- %s\n", *extrasPtr++); 117 | } 118 | 119 | #undef P 120 | 121 | exit_code = 0; 122 | exit: 123 | free(extras); /* DO NOT free individual strings */ 124 | return exit_code; 125 | xopt_help: 126 | exit_code = 2; 127 | goto exit; 128 | } 129 | -------------------------------------------------------------------------------- /test/simple-1.out: -------------------------------------------------------------------------------- 1 | args: «--some-int=10» «--some-double=14.5» «foo» «bar» «--» «--some-other=20» 2 | 3 | someInt: 10 4 | someFloat: 0.000000 5 | someDouble: 14.500000 6 | help: 0 7 | 8 | extra count: 3 9 | - foo 10 | - bar 11 | - --some-other=20 12 | -------------------------------------------------------------------------------- /test/simple.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../xopt.h" 6 | 7 | typedef struct { 8 | int someInt; 9 | float someFloat; 10 | double someDouble; 11 | bool help; 12 | } SimpleConfig; 13 | 14 | xoptOption options[] = { 15 | { 16 | "some-int", 17 | 'i', 18 | offsetof(SimpleConfig, someInt), 19 | 0, 20 | XOPT_TYPE_INT, 21 | "n", 22 | "Some integer value. Can set to whatever number you like." 23 | }, 24 | { 25 | "some-float", 26 | 'f', 27 | offsetof(SimpleConfig, someFloat), 28 | 0, 29 | XOPT_TYPE_FLOAT, 30 | "n", 31 | "Some float value." 32 | }, 33 | { 34 | "some-double", 35 | 'd', 36 | offsetof(SimpleConfig, someDouble), 37 | 0, 38 | XOPT_TYPE_DOUBLE, 39 | "n", 40 | "Some double value." 41 | }, 42 | { 43 | "help", 44 | '?', 45 | offsetof(SimpleConfig, help), 46 | 0, 47 | XOPT_TYPE_BOOL, 48 | 0, 49 | "Shows this help message" 50 | }, 51 | XOPT_NULLOPTION 52 | }; 53 | 54 | int main(int argc, const char **argv) { 55 | int result; 56 | const char *err; 57 | xoptContext *ctx; 58 | SimpleConfig config; 59 | const char **extras = 0; 60 | const char **extrasPtr = 0; 61 | int extraCount; 62 | 63 | result = 0; 64 | err = 0; 65 | 66 | /* show arguments */ 67 | fputs("args:", stderr); 68 | for (int i = 1; i < argc; i++) { 69 | fprintf(stderr, " «%s»", argv[i]); 70 | } 71 | fputs("\n\n", stderr); 72 | 73 | /* setup defaults */ 74 | config.someInt = 0; 75 | config.someDouble = 0.0; 76 | config.help = 0; 77 | 78 | /* create context */ 79 | ctx = xopt_context("xopt-test", options, 80 | XOPT_CTX_POSIXMEHARDER | XOPT_CTX_STRICT, &err); 81 | if (err) { 82 | fprintf(stderr, "Error: %s\n", err); 83 | result = 1; 84 | goto exit; 85 | } 86 | 87 | /* parse */ 88 | extraCount = xopt_parse(ctx, argc, argv, &config, &extras, &err); 89 | if (err) { 90 | fprintf(stderr, "Error: %s\n", err); 91 | result = 2; 92 | goto exit; 93 | } 94 | 95 | /* help? */ 96 | if (config.help) { 97 | xoptAutohelpOptions opts; 98 | opts.usage = "[options] [extras...]"; 99 | opts.prefix = "A simple demonstration of the XOpt options parser library."; 100 | opts.suffix = "End argument list."; 101 | opts.spacer = 10; 102 | 103 | xopt_autohelp(ctx, stderr, &opts, &err); 104 | goto exit; 105 | } 106 | 107 | /* print */ 108 | #define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \ 109 | config.field) 110 | 111 | P(someInt, d); 112 | P(someFloat, f); 113 | P(someDouble, f); 114 | P(help, d); 115 | 116 | fprintf(stderr, "\nextra count: %d\n", extraCount); 117 | extrasPtr = extras; 118 | while (extraCount--) { 119 | fprintf(stderr, "- %s\n", *extrasPtr++); 120 | } 121 | 122 | #undef P 123 | 124 | exit: 125 | if (extras) free(extras); /* DO NOT free individual strings */ 126 | if (ctx) free(ctx); /* they point to argv strings */ 127 | return result; 128 | } 129 | -------------------------------------------------------------------------------- /test/sloppyshorts-1.out: -------------------------------------------------------------------------------- 1 | args: «-i10» «-d» «14.5» «-ssome string» «-m» «-mm» «-mmm» «foo» «bar» «--» «--is-not-passed» «ignoreme» 2 | 3 | someInt: 10 4 | someFloat: 0.000000 5 | someDouble: 14.500000 6 | someString: some string 7 | multiCount: 6 8 | help: 0 9 | 10 | extra count: 4 11 | - foo 12 | - bar 13 | - --is-not-passed 14 | - ignoreme 15 | -------------------------------------------------------------------------------- /test/sloppyshorts.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../xopt.h" 6 | 7 | typedef struct { 8 | int someInt; 9 | float someFloat; 10 | double someDouble; 11 | const char *someString; 12 | int multiCount; 13 | bool help; 14 | } SimpleConfig; 15 | 16 | static void on_verbose(const char *v, void *data, const struct xoptOption *option, bool longArg, const char **err) { 17 | (void) v; 18 | (void) longArg; 19 | (void) err; 20 | int *count = (int *)(((char *) data) + option->offset); 21 | ++*count; 22 | } 23 | 24 | xoptOption options[] = { 25 | { 26 | "some-int", 27 | 'i', 28 | offsetof(SimpleConfig, someInt), 29 | 0, 30 | XOPT_TYPE_INT, 31 | "n", 32 | "Some integer value. Can set to whatever number you like." 33 | }, 34 | { 35 | "some-float", 36 | 'f', 37 | offsetof(SimpleConfig, someFloat), 38 | 0, 39 | XOPT_TYPE_FLOAT, 40 | "n", 41 | "Some float value." 42 | }, 43 | { 44 | "some-double", 45 | 'd', 46 | offsetof(SimpleConfig, someDouble), 47 | 0, 48 | XOPT_TYPE_DOUBLE, 49 | "n", 50 | "Some double value." 51 | }, 52 | { 53 | "some-string", 54 | 's', 55 | offsetof(SimpleConfig, someString), 56 | 0, 57 | XOPT_TYPE_STRING, 58 | "s", 59 | "Some string value." 60 | }, 61 | { 62 | 0, 63 | 'm', 64 | offsetof(SimpleConfig, multiCount), 65 | &on_verbose, 66 | XOPT_TYPE_BOOL, 67 | 0, 68 | "Specify multiple times to increase count" 69 | }, 70 | { 71 | "help", 72 | '?', 73 | offsetof(SimpleConfig, help), 74 | 0, 75 | XOPT_TYPE_BOOL, 76 | 0, 77 | "Shows this help message" 78 | }, 79 | XOPT_NULLOPTION 80 | }; 81 | 82 | int main(int argc, const char **argv) { 83 | int exit_code = 1; 84 | const char *err = NULL; 85 | SimpleConfig config; 86 | const char **extras = NULL; 87 | const char **extrasPtr = NULL; 88 | int extraCount = 0; 89 | 90 | /* show arguments */ 91 | fputs("args:", stderr); 92 | for (int i = 1; i < argc; i++) { 93 | fprintf(stderr, " «%s»", argv[i]); 94 | } 95 | fputs("\n\n", stderr); 96 | 97 | /* setup defaults */ 98 | config.someInt = 0; 99 | config.someDouble = 0.0; 100 | config.someFloat = 0.0f; 101 | config.someString = 0; 102 | config.multiCount = 0; 103 | config.help = 0; 104 | 105 | XOPT_SIMPLE_PARSE( 106 | argv[0], 107 | XOPT_CTX_SLOPPYSHORTS, 108 | &options[0], &config, 109 | argc, argv, 110 | &extraCount, &extras, 111 | &err, 112 | stderr, 113 | "[opts...] [--] [extras...]", 114 | "Tests the simple parser macro", 115 | "[end of arguments]", 116 | 15); 117 | 118 | if (err) { 119 | fprintf(stderr, "Error: %s\n", err); 120 | goto exit; 121 | } 122 | 123 | /* print */ 124 | #define P(field, delim) fprintf(stderr, #field ":\t%" #delim "\n", \ 125 | config.field) 126 | 127 | P(someInt, d); 128 | P(someFloat, f); 129 | P(someDouble, f); 130 | P(someString, s); 131 | P(multiCount, d); 132 | P(help, d); 133 | 134 | fprintf(stderr, "\nextra count: %d\n", extraCount); 135 | extrasPtr = extras; 136 | while (extraCount--) { 137 | fprintf(stderr, "- %s\n", *extrasPtr++); 138 | } 139 | 140 | #undef P 141 | 142 | exit_code = 0; 143 | exit: 144 | free(extras); /* DO NOT free individual strings */ 145 | return exit_code; 146 | xopt_help: 147 | exit_code = 2; 148 | goto exit; 149 | } 150 | -------------------------------------------------------------------------------- /test/test-case.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # To be used with CMake builds located in /build/ 5 | # 6 | 7 | set -uo pipefail 8 | exec >&2 9 | 10 | casebin="$1" 11 | caseout="$2" 12 | shift 2 13 | 14 | function die { 15 | echo -e "error: $*" 16 | exit 1 17 | } 18 | 19 | cd "$(dirname "$0")" 20 | 21 | if [ -z "$casebin" ]; then 22 | die 'no test case executable specified' 23 | fi 24 | 25 | if [ -z "$caseout" ]; then 26 | die 'no test case output (some-case.out) specified' 27 | fi 28 | 29 | if [ ! -x "$casebin" ]; then 30 | die "test case does not exist or is not executable: $casebin" 31 | fi 32 | 33 | if [ ! -f "$caseout" ]; then 34 | die "test case expected output file does not exist: $caseout" 35 | fi 36 | 37 | output="$("$casebin" "$@" 2>&1)" 38 | r=$? 39 | if [ $r -eq 139 ]; then 40 | die "xopt test case failed with SEGFAULT ($r)" 41 | fi 42 | 43 | diff="$(diff -U0 -d -t "$caseout" - <<< "$output" 2>&1)" 44 | if [ ! -z "$diff" ]; then 45 | die "xopt test case didn't match expected output: '$caseout'\n$diff" 46 | fi 47 | -------------------------------------------------------------------------------- /xopt.c: -------------------------------------------------------------------------------- 1 | /** 2 | * XOpt - command line parsing library 3 | * 4 | * Copyright (c) 2015-2019 Josh Junon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | #ifndef XOPT_NOSTANDARD 26 | # define HAVE_STDARG_H 1 27 | # define HAVE_STDLIB_H 1 28 | # define HAVE_ASPRINTF_H 1 29 | # define vasprintf rpl_vasprintf 30 | # ifndef _GNU_SOURCE 31 | # define _GNU_SOURCE 32 | # endif 33 | #endif 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #include "./xopt.h" 44 | #include "./snprintf.c" 45 | 46 | #define EXTRAS_INIT 10 47 | #define ERRBUF_SIZE 1024 * 4 48 | 49 | static char errbuf[ERRBUF_SIZE]; 50 | 51 | struct xoptContext { 52 | const xoptOption *options; 53 | long flags; 54 | const char *name; 55 | bool doubledash; 56 | size_t options_count; 57 | bool *required; 58 | jmp_buf *jmp; 59 | }; 60 | 61 | static void _xopt_set_err(xoptContext *ctx, const char **err, const char *const fmt, ...); 62 | static int _xopt_parse_arg(xoptContext *ctx, int argc, const char **argv, 63 | int *argi, void *data, const char **err); 64 | static void _xopt_assert_increment(xoptContext *ctx, const char ***extras, int extrasCount, 65 | size_t *extrasCapac, const char **err); 66 | static int _xopt_get_size(const char *arg); 67 | static int _xopt_get_arg(const xoptContext *ctx, const char *arg, size_t len, 68 | int size, const xoptOption **option, size_t *option_index); 69 | static void _xopt_set(xoptContext *ctx, void *data, const xoptOption *option, const char *val, 70 | bool longArg, const char **err); 71 | static void _xopt_default_callback(const char *value, void *data, 72 | const xoptOption *option, bool longArg, const char **err); 73 | 74 | xoptContext* xopt_context(const char *name, const xoptOption *options, long flags, 75 | const char **err) { 76 | xoptContext* ctx; 77 | *err = 0; 78 | 79 | /* malloc context and check */ 80 | ctx = malloc(sizeof(xoptContext)); 81 | if (!ctx) { 82 | ctx = 0; 83 | _xopt_set_err(NULL, err, "could not allocate context"); 84 | } else { 85 | const xoptOption *cur; 86 | 87 | ctx->options = options; 88 | ctx->flags = flags; 89 | ctx->name = name; 90 | ctx->doubledash = false; 91 | ctx->required = NULL; 92 | ctx->jmp = NULL; 93 | 94 | ctx->options_count = 0; 95 | cur = options; 96 | for (; cur->longArg || cur->shortArg; cur++) ++ctx->options_count; 97 | } 98 | 99 | return ctx; 100 | } 101 | 102 | static int _xopt_parse_impl(xoptContext *ctx, int argc, const char **argv, void *data, 103 | const char ***inextras, const char **err, int *extrasCount, size_t *extrasCapac, 104 | const char ***extras, int *argi) { 105 | int parseResult; 106 | size_t i; 107 | 108 | *err = 0; 109 | *argi = 0; 110 | *extrasCount = 0; 111 | *extrasCapac = EXTRAS_INIT; 112 | *extras = malloc(sizeof(**extras) * EXTRAS_INIT); 113 | 114 | jmp_buf jmp; 115 | ctx->jmp = &jmp; 116 | if (setjmp(jmp)) { 117 | goto end; 118 | } 119 | 120 | /* check if extras malloc'd okay */ 121 | if (!*extras) { 122 | _xopt_set_err(ctx, err, "could not allocate extras array"); 123 | } 124 | 125 | /* increment argument counter if we aren't 126 | instructed to check argv[0] */ 127 | if (!(ctx->flags & XOPT_CTX_KEEPFIRST)) { 128 | ++(*argi); 129 | } 130 | 131 | /* set up required parameters list */ 132 | ctx->required = malloc(sizeof(*ctx->required) * ctx->options_count); 133 | for (i = 0; i < ctx->options_count; i++) { 134 | ctx->required[i] = (ctx->options[i].options & XOPT_REQUIRED) > 0; 135 | } 136 | 137 | /* iterate over passed command line arguments */ 138 | for (; *argi < argc; (*argi)++) { 139 | /* parse, breaking if there was a failure 140 | parseResult is 0 if option, 1 if extra, or 2 if double-dash was encountered */ 141 | parseResult = _xopt_parse_arg(ctx, argc, argv, argi, data, err); 142 | 143 | /* is the argument an extra? */ 144 | switch (parseResult) { 145 | case 0: /* option */ 146 | /* make sure we're super-posix'd if specified to be 147 | (check that no extras have been specified when an option is parsed, 148 | enforcing options to be specific before [extra] arguments */ 149 | if ((ctx->flags & XOPT_CTX_POSIXMEHARDER) && *extrasCount) { 150 | _xopt_set_err(ctx, err, "options cannot be specified after arguments: %s", argv[*argi]); 151 | goto end; 152 | } 153 | break; 154 | case 1: /* extra */ 155 | /* make sure we have enough room, or realloc if we don't - 156 | check that it succeeded */ 157 | _xopt_assert_increment(ctx, extras, *extrasCount, extrasCapac, err); 158 | 159 | /* add extra to list */ 160 | (*extras)[(*extrasCount)++] = argv[*argi]; 161 | break; 162 | case 2: /* "--" was encountered */ 163 | /* nothing to do here - "--" was already handled for us */ 164 | break; 165 | } 166 | } 167 | 168 | end: 169 | if (!*err) { 170 | for (i = 0; i < ctx->options_count; i++) { 171 | if (ctx->required[i]) { 172 | const xoptOption *opt = &ctx->options[i]; 173 | if (opt->longArg) { 174 | _xopt_set_err(ctx, err, "missing required option: --%s", opt->longArg); 175 | } else { 176 | _xopt_set_err(ctx, err, "missing required option: -%c", opt->shortArg); 177 | } 178 | break; 179 | } 180 | } 181 | } 182 | 183 | free(ctx->required); 184 | 185 | if (!*err) { 186 | /* append null terminator to extras */ 187 | _xopt_assert_increment(ctx, extras, *extrasCount, extrasCapac, err); 188 | if (!*err) { 189 | (*extras)[*extrasCount] = 0; 190 | } 191 | } 192 | 193 | if (*err) { 194 | free(*extras); 195 | *inextras = 0; 196 | return 0; 197 | } 198 | 199 | *inextras = *extras; 200 | return *extrasCount; 201 | } 202 | 203 | int xopt_parse(xoptContext *ctx, int argc, const char **argv, void *data, 204 | const char ***inextras, const char **err) { 205 | /* avoid longjmp clobbering */ 206 | int extrasCount; 207 | size_t extrasCapac; 208 | const char **extras; 209 | int argi; 210 | return _xopt_parse_impl(ctx, argc, argv, data, inextras, err, &extrasCount, &extrasCapac, &extras, &argi); 211 | } 212 | 213 | void xopt_autohelp(xoptContext *ctx, FILE *stream, const xoptAutohelpOptions *options, 214 | const char **err) { 215 | const xoptOption *o; 216 | size_t i, width = 0, twidth; 217 | const char *nl = ""; 218 | size_t spacer = options ? options->spacer : 2; 219 | 220 | *err = 0; 221 | 222 | /* make sure that if we ever write a call to _set_err() in the future here, 223 | that we won't accidentally cause segfaults - we have an assertion in place 224 | for ctx->jmp != NULL, so we make sure we'd trigger that assertion */ 225 | ctx->jmp = NULL; 226 | 227 | if (options && options->usage) { 228 | fprintf(stream, "%susage: %s %s\n", nl, ctx->name, options->usage); 229 | nl = "\n"; 230 | } 231 | 232 | if (options && options->prefix) { 233 | fprintf(stream, "%s%s\n\n", nl, options->prefix); 234 | nl = "\n"; 235 | } 236 | 237 | /* find max width */ 238 | for (i = 0; ctx->options[i].longArg || ctx->options[i].shortArg; i++) { 239 | o = &ctx->options[i]; 240 | twidth = 0; 241 | if (o->longArg) { 242 | twidth += 2 + strlen(o->longArg); 243 | if (o->argDescrip) { 244 | twidth += 1 + strlen(o->argDescrip); 245 | } 246 | } 247 | if (ctx->options[i].shortArg) { 248 | twidth += 2; 249 | } 250 | if (ctx->options[i].shortArg && ctx->options[i].longArg) { 251 | twidth += 2; /* `, ` */ 252 | } 253 | 254 | width = width > twidth ? width : twidth; 255 | } 256 | 257 | /* print */ 258 | for (i = 0; ctx->options[i].longArg || ctx->options[i].shortArg; i++) { 259 | o = &ctx->options[i]; 260 | twidth = 0; 261 | if (o->shortArg) { 262 | fprintf(stream, "-%c", o->shortArg); 263 | twidth += 2; 264 | } 265 | 266 | if (o->shortArg && o->longArg) { 267 | fprintf(stream, ", "); 268 | twidth += 2; 269 | } 270 | 271 | if (o->longArg) { 272 | fprintf(stream, "--%s", o->longArg); 273 | twidth += 2 + strlen(o->longArg); 274 | if (o->argDescrip) { 275 | fprintf(stream, "=%s", o->argDescrip); 276 | twidth += 1 + strlen(o->argDescrip); 277 | } 278 | } 279 | 280 | if (o->descrip) { 281 | for (; twidth < (width + spacer); twidth++) { 282 | fprintf(stream, " "); 283 | } 284 | 285 | if (o->options & XOPT_REQUIRED) { 286 | fprintf(stream, "(Required) %s\n", o->descrip); 287 | } else { 288 | fprintf(stream, "%s\n", o->descrip); 289 | } 290 | } 291 | } 292 | 293 | if (options && options->suffix) { 294 | fprintf(stream, "%s%s\n", nl, options->suffix); 295 | } 296 | } 297 | 298 | static void _xopt_set_err(xoptContext *ctx, const char **err, const char *const fmt, ...) { 299 | va_list list; 300 | va_start(list, fmt); 301 | rpl_vsnprintf(&errbuf[0], ERRBUF_SIZE, fmt, list); 302 | va_end(list); 303 | *err = &errbuf[0]; 304 | 305 | if (ctx != NULL) { 306 | assert(ctx->jmp != NULL); 307 | longjmp(*ctx->jmp, 1); 308 | } 309 | } 310 | 311 | static int _xopt_parse_arg(xoptContext *ctx, int argc, const char **argv, 312 | int *argi, void *data, const char **err) { 313 | int size; 314 | size_t length; 315 | bool isExtra = false; 316 | const xoptOption *option = NULL; 317 | size_t option_index = 0; 318 | const char* arg = argv[*argi]; 319 | 320 | /* are we in doubledash mode? */ 321 | if (ctx->doubledash) { 322 | return 1; 323 | } 324 | 325 | /* get argument 'size' (long/short/extra) */ 326 | size = _xopt_get_size(arg); 327 | 328 | /* adjust to parse from beginning of actual content */ 329 | arg += size; 330 | length = strlen(arg); 331 | 332 | if (size == 1 && length == 0) { 333 | /* it's just a singular dash - treat it as an extra arg */ 334 | return 1; 335 | } 336 | 337 | if (size == 2 && length == 0) { 338 | /* double-dash - everything after this is an extra */ 339 | ctx->doubledash = 1; 340 | return 2; 341 | } 342 | 343 | switch (size) { 344 | int argRequirement; 345 | char *valStart; 346 | case 1: /* short */ 347 | /* parse all */ 348 | while (length--) { 349 | /* get argument or error if not found and strict mode enabled. */ 350 | argRequirement = _xopt_get_arg(ctx, arg++, 1, size, &option, &option_index); 351 | if (!option) { 352 | if (ctx->flags & XOPT_CTX_STRICT) { 353 | _xopt_set_err(ctx, err, "invalid option: -%c", arg[-1]); 354 | } 355 | break; 356 | } 357 | 358 | if (argRequirement > 0 && length > 0 && !(ctx->flags & XOPT_CTX_SLOPPYSHORTS)) { 359 | _xopt_set_err(ctx, err, "short option parameters must be separated, not condensed: %s", argv[*argi]); 360 | } 361 | 362 | switch (argRequirement) { 363 | case 0: /* flag; doesn't take an argument */ 364 | if (length > 0 && (ctx->flags & XOPT_CTX_NOCONDENSE)) { 365 | _xopt_set_err(ctx, err, "short options cannot be combined: %s", argv[*argi]); 366 | } 367 | 368 | _xopt_set(ctx, data, option, 0, false, err); 369 | break; 370 | case 1: /* argument is optional */ 371 | /* is there another argument, and is it a non-option? */ 372 | if (*argi + 1 < argc && _xopt_get_size(argv[*argi + 1]) == 0) { 373 | _xopt_set(ctx, data, option, argv[++*argi], false, err); 374 | } else { 375 | _xopt_set(ctx, data, option, 0, false, err); 376 | } 377 | break; 378 | case 2: /* requires an argument */ 379 | /* is it the last in a set of condensed options? */ 380 | if (length == 0) { 381 | /* is there another argument? */ 382 | if (*argi + 1 < argc) { 383 | /* is the next argument actually an option? 384 | this indicates no value was passed */ 385 | if (_xopt_get_size(argv[*argi + 1])) { 386 | _xopt_set_err(ctx, err, "missing option value: -%c", 387 | option->shortArg); 388 | } else { 389 | _xopt_set(ctx, data, option, argv[++*argi], false, err); 390 | } 391 | } else { 392 | _xopt_set_err(ctx, err, "missing option value: -%c", 393 | option->shortArg); 394 | } 395 | } else { 396 | _xopt_set(ctx, data, option, arg, false, err); 397 | length = 0; 398 | } 399 | break; 400 | } 401 | } 402 | break; 403 | case 2: /* long */ 404 | /* find first equals sign */ 405 | valStart = strchr(arg, '='); 406 | 407 | /* is there a value? */ 408 | if (valStart) { 409 | /* we also increase valStart here in order to lop off 410 | the equals sign */ 411 | length = valStart++ - arg; 412 | 413 | /* but not really, if it's null */ 414 | if (!*valStart) { 415 | valStart = 0; 416 | } 417 | } 418 | 419 | /* get the option */ 420 | argRequirement = _xopt_get_arg(ctx, arg, length, size, &option, &option_index); 421 | if (!option) { 422 | _xopt_set_err(ctx, err, "invalid option: --%.*s", length, arg); 423 | } else { 424 | switch (argRequirement) { 425 | case 0: /* flag; doesn't take an argument */ 426 | if (valStart) { 427 | _xopt_set_err(ctx, err, "option doesn't take a value: --%s", arg); 428 | } 429 | 430 | _xopt_set(ctx, data, option, valStart, true, err); 431 | break; 432 | case 2: /* requires an argument */ 433 | if (!valStart) { 434 | _xopt_set_err(ctx, err, "missing option value: --%s", arg); 435 | } 436 | break; 437 | } 438 | 439 | _xopt_set(ctx, data, option, valStart, true, err); 440 | } 441 | 442 | break; 443 | case 0: /* extra */ 444 | isExtra = true; 445 | break; 446 | } 447 | 448 | if (option) { 449 | /* indicate that we've seen this option and thus is no longer required */ 450 | ctx->required[option_index] = false; 451 | } 452 | 453 | return isExtra ? 1 : 0; 454 | } 455 | 456 | static void _xopt_assert_increment(xoptContext *ctx, const char ***extras, int extrasCount, 457 | size_t *extrasCapac, const char **err) { 458 | /* have we hit the list size limit? */ 459 | if ((size_t) extrasCount == *extrasCapac) { 460 | /* increase capcity, realloc, and check for success */ 461 | *extrasCapac += EXTRAS_INIT; 462 | *extras = realloc(*extras, sizeof(**extras) * *extrasCapac); 463 | if (!*extras) { 464 | _xopt_set_err(ctx, err, "could not realloc arguments array"); 465 | } 466 | } 467 | } 468 | 469 | static int _xopt_get_size(const char *arg) { 470 | int size; 471 | for (size = 0; size < 2; size++) { 472 | if (arg[size] != '-') { 473 | break; 474 | } 475 | } 476 | return size; 477 | } 478 | 479 | static int _xopt_get_arg(const xoptContext *ctx, const char *arg, size_t len, 480 | int size, const xoptOption **option, size_t *option_index) { 481 | size_t i; 482 | 483 | *option = 0; 484 | 485 | /* find the argument */ 486 | for (i = 0; i < ctx->options_count; i++) { 487 | const xoptOption *opt = &ctx->options[i]; 488 | 489 | if ((size == 1 && opt->shortArg == arg[0]) 490 | || (opt->longArg && strlen(opt->longArg) == len && !strncmp(opt->longArg, arg, len))) { 491 | *option_index = i; 492 | *option = opt; 493 | break; 494 | } 495 | } 496 | 497 | /* determine the optionality of a value */ 498 | if (!*option || (*option)->options & XOPT_TYPE_BOOL) { 499 | return 0; 500 | } else if ((*option)->options & XOPT_PARAM_OPTIONAL) { 501 | return 1; 502 | } else { 503 | return 2; 504 | } 505 | } 506 | 507 | static void _xopt_set(xoptContext *ctx, void *data, const xoptOption *option, const char *val, 508 | bool longArg, const char **err) { 509 | /* determine callback */ 510 | xoptCallback callback = option->callback ? option->callback : &_xopt_default_callback; 511 | 512 | /* dispatch callback */ 513 | callback(val, data, option, longArg, err); 514 | 515 | /* we check err here instead of relying upon longjmp() 516 | since we can't call _set_err() with a context */ 517 | if (*err) { 518 | assert(ctx->jmp != NULL); 519 | longjmp(*ctx->jmp, 1); 520 | } 521 | } 522 | 523 | static void _xopt_default_callback(const char *value, void *data, 524 | const xoptOption *option, bool longArg, const char **err) { 525 | void *target; 526 | char *parsePtr = 0; 527 | 528 | /* is a value specified? */ 529 | if ((!value || !strlen(value)) && !(option->options & XOPT_TYPE_BOOL)) { 530 | /* we reach this point when they specified an optional, non-boolean 531 | option but didn't specify a custom handler (therefore, it's not 532 | optional). 533 | 534 | to fix, just remove the optional flag or specify a callback to handle 535 | it yourself. 536 | */ 537 | return; 538 | } 539 | 540 | /* get location */ 541 | target = ((char*) data) + option->offset; 542 | 543 | /* switch on the type */ 544 | switch (option->options & 0x3F) { 545 | case XOPT_TYPE_BOOL: 546 | /* booleans are special in that they won't have an argument passed 547 | into this callback */ 548 | *((_Bool*) target) = true; 549 | break; 550 | case XOPT_TYPE_STRING: 551 | /* lifetime here works out fine; argv can usually be assumed static-like 552 | in nature */ 553 | *((const char**) target) = value; 554 | break; 555 | case XOPT_TYPE_INT: 556 | *((int*) target) = (int) strtol(value, &parsePtr, 0); 557 | break; 558 | case XOPT_TYPE_LONG: 559 | *((long*) target) = strtol(value, &parsePtr, 0); 560 | break; 561 | case XOPT_TYPE_FLOAT: 562 | *((float*) target) = (float) strtod(value, &parsePtr); 563 | break; 564 | case XOPT_TYPE_DOUBLE: 565 | *((double*) target) = strtod(value, &parsePtr); 566 | break; 567 | default: /* something wonky, or the implementation specifies two types */ 568 | fprintf(stderr, "warning: XOpt argument type invalid: %ld\n", 569 | option->options & 0x2F); 570 | break; 571 | } 572 | 573 | /* check that our parsing functions worked */ 574 | if (parsePtr && *parsePtr) { 575 | if (longArg) { 576 | _xopt_set_err(NULL, err, "value isn't a valid number: --%s=%s", 577 | (void*) option->longArg, value); 578 | } else { 579 | _xopt_set_err(NULL, err, "value isn't a valid number: -%c %s", 580 | option->shortArg, value); 581 | } 582 | } 583 | } 584 | 585 | -------------------------------------------------------------------------------- /xopt.h: -------------------------------------------------------------------------------- 1 | /** 2 | * XOpt - command line parsing library 3 | * 4 | * Copyright (c) 2015 Josh Junon. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | #ifndef XOPT_H__ 25 | #define XOPT_H__ 26 | #pragma once 27 | 28 | #include 29 | #include 30 | 31 | struct xoptOption; 32 | 33 | #ifndef offsetof 34 | # define offsetof(T, member) (size_t)(&(((T*)0)->member)) 35 | #endif 36 | 37 | /** 38 | * Callback type for handling values. 39 | * Called when a command line argument has been 40 | * processed. 41 | */ 42 | typedef void (*xoptCallback)( 43 | const char *value, /* string cmd line option */ 44 | void *data, /* custom data structure */ 45 | const struct xoptOption *option, /* detected option */ 46 | bool longArg, /* true if the long-arg version 47 | was used */ 48 | const char **err); /* err output */ 49 | 50 | enum xoptOptionFlag { 51 | XOPT_TYPE_STRING = 0x1, /* const char* type */ 52 | XOPT_TYPE_INT = 0x2, /* int type */ 53 | XOPT_TYPE_LONG = 0x4, /* long type */ 54 | XOPT_TYPE_FLOAT = 0x8, /* float type */ 55 | XOPT_TYPE_DOUBLE = 0x10, /* double type */ 56 | XOPT_TYPE_BOOL = 0x20, /* boolean (int) type */ 57 | 58 | XOPT_PARAM_OPTIONAL = 0x40, /* whether the argument value is 59 | optional */ 60 | XOPT_REQUIRED = 0x80 /* indicates the flag must be 61 | present on the command line */ 62 | }; 63 | 64 | enum xoptContextFlag { 65 | XOPT_CTX_KEEPFIRST = 0x1, /* don't ignore argv[0] */ 66 | XOPT_CTX_POSIXMEHARDER = 0x2, /* options cannot come after 67 | extra arguments */ 68 | XOPT_CTX_NOCONDENSE = 0x4, /* don't allow short args to be 69 | condensed (i.e. `ls -laF') */ 70 | XOPT_CTX_SLOPPYSHORTS = 0x8, /* allow short arg values to be 71 | directly after the character */ 72 | XOPT_CTX_STRICT = 0x10 /* fails on invalid arguments */ 73 | }; 74 | 75 | typedef struct xoptOption { 76 | const char *longArg; /* --long-arg-name, or 0 for short 77 | arg only */ 78 | const char shortArg; /* -s hort arg character, or '\0' 79 | for long arg only */ 80 | size_t offset; /* offsetof(type, property) for 81 | automatic configuration handler */ 82 | xoptCallback callback; /* callback for resolved option 83 | handling */ 84 | long options; /* xoptOptionFlag options */ 85 | const char *argDescrip; /* --argument=argDescrip (autohelp) */ 86 | const char *descrip; /* argument explanation (autohelp) */ 87 | } xoptOption; 88 | 89 | /* option list terminator */ 90 | #define XOPT_NULLOPTION {0, 0, 0, 0, 0, 0, 0} 91 | 92 | typedef struct xoptContext xoptContext; 93 | 94 | typedef struct xoptAutohelpOptions { 95 | const char *usage; /* usage string, or null */ 96 | const char *prefix; /* printed before options, or null */ 97 | const char *suffix; /* printed after options, or null */ 98 | size_t spacer; /* number of spaces between option and 99 | description */ 100 | } xoptAutohelpOptions; 101 | 102 | #ifdef __cplusplus 103 | extern "C" { 104 | #endif 105 | 106 | /** 107 | * Creates an XOpt context to be used with 108 | * subsequent calls to XOpt functions 109 | */ 110 | xoptContext* 111 | xopt_context( 112 | const char *name, /* name of the argument set (usually 113 | name of the cli binary file/cmd */ 114 | const xoptOption *options, /* list of xoptOption objects, 115 | terminated with XOPT_NULLOPTION */ 116 | long flags, /* xoptContextFlag flags */ 117 | const char **err); /* pointer to a const char* that 118 | receives an err should one occur - 119 | set to 0 if command completed 120 | successfully */ 121 | 122 | /** 123 | * Parses the command line of a program 124 | * and returns the number of non-options 125 | * returned to the `extras' pointer (see 126 | * below) 127 | */ 128 | int 129 | xopt_parse( 130 | xoptContext *ctx, /* previously created XOpt context */ 131 | int argc, /* argc, from int main() */ 132 | const char **argv, /* argv, from int main() */ 133 | void *data, /* a custom data object whos type 134 | corresponds to `.offset' values 135 | specified in the options list; 136 | populated with values interpreted 137 | from the command line */ 138 | const char ***extras, /* receives a list of extra non-option 139 | arguments (i.e. files, subcommands, 140 | etc.) - length of which is returned 141 | by the function call */ 142 | const char **err); /* pointer to a const char* that 143 | receives an err should one occur - 144 | set to 0 if command completed 145 | successfully */ 146 | 147 | /** 148 | * Generates and prints a help message 149 | * and prints it to a FILE stream. 150 | * If `defaults' is supplied, uses 151 | * offsets (values) defined by the options 152 | * list to show default options 153 | */ 154 | void 155 | xopt_autohelp( 156 | xoptContext *ctx, /* previously created XOpt context */ 157 | FILE *stream, /* a stream to print to - if 0, 158 | defaults to `stderr'. */ 159 | const xoptAutohelpOptions *options, /* configuration options to tailor 160 | autohelp output */ 161 | const char **err); /* pointer to a const char* that 162 | receives an err should one occur - 163 | set to 0 if command completed 164 | successfully */ 165 | 166 | /** 167 | * Generates a default option parser that's sane for most cases. 168 | * 169 | * Assumes there's a `help` property that is boolean-checkable that exists on the 170 | * config pointer passed to `config_ptr` (i.e. does a lookup of `config_ptr->help`). 171 | * 172 | * In the event help is invoked, xopt will `goto xopt_help`. It is up to you to define such 173 | * a label in order to recover. In this case, extrav will still be allocated and will still need to be 174 | * freed. 175 | * 176 | * To be extra clear, you need to free `extrav_ptr` is if `*err_ptr` is not `NULL`. 177 | * 178 | * `name` is the name of the binary you'd like to pass to the context (welcome to use `argv[0]` here), 179 | * `options` is a reference to the xoptOptions array you've specified, 180 | * `config_ptr` is a *pointer* to your configuration instance, 181 | * `argc` and `argv` are the int/const char ** passed into main, 182 | * `extrac_ptr` and `extrav_ptr` are pointers to an `int`/`const char **` 183 | * (so `int*` and `const char ***`, respectively) that receive the parsed extra args 184 | * (note that, unless there is an error, `extrav_ptr` is owned by your program and must 185 | * be `free()`'d when you're done using it, even if there are zero extra arguments), 186 | * and `err_ptr` is a pointer to a `const char *` (so a `const char **`) that receives any error 187 | * strings in the event of a problem. These errors are statically allocated so no need to 188 | * free them. This variable should be initialized to NULL and checked after calling 189 | * `XOPT_SIMPLE_PARSE()`. 190 | * 191 | * `autohelp_file`, `autohelp_usage`, `autohelp_prefix`, `autohelp_suffix` and `autohelp_spacer` are all 192 | * parameters to the `xoptAutohelpOptions` struct (with the exception of `autohelp_file`, which must be a 193 | * `FILE*` reference (e.g. `stdout` or `stderr`) which receives the rendered autohelp text). Consult the 194 | * `xoptAutohelpOptions` struct above for documentation as to valid values for each of these properties. 195 | */ 196 | #define XOPT_SIMPLE_PARSE(name, flags, options, config_ptr, argc, argv, extrac_ptr, extrav_ptr, err_ptr, autohelp_file, autohelp_usage, autohelp_prefix, autohelp_suffix, autohelp_spacer) do { \ 197 | xoptContext *_xopt_ctx; \ 198 | *(err_ptr) = NULL; \ 199 | _xopt_ctx = xopt_context((name), (options), ((flags) ^ XOPT_CTX_POSIXMEHARDER ^ XOPT_CTX_STRICT), (err_ptr)); \ 200 | if (*(err_ptr)) break; \ 201 | *extrac_ptr = xopt_parse(_xopt_ctx, (argc), (argv), (config_ptr), (extrav_ptr), (err_ptr)); \ 202 | if ((config_ptr)->help) { \ 203 | xoptAutohelpOptions __xopt_autohelp_opts; \ 204 | __xopt_autohelp_opts.usage = (autohelp_usage); \ 205 | __xopt_autohelp_opts.prefix = (autohelp_prefix); \ 206 | __xopt_autohelp_opts.suffix = (autohelp_suffix); \ 207 | __xopt_autohelp_opts.spacer = (autohelp_spacer); \ 208 | xopt_autohelp(_xopt_ctx, (autohelp_file), &__xopt_autohelp_opts, (err_ptr)); \ 209 | if (*(err_ptr)) goto __xopt_end_free_extrav; \ 210 | goto xopt_help; \ 211 | } \ 212 | if (*(err_ptr)) goto __xopt_end_free_ctx; \ 213 | __xopt_end_free_ctx: \ 214 | free(_xopt_ctx); \ 215 | break; \ 216 | __xopt_end_free_extrav: \ 217 | free(*(extrav_ptr)); \ 218 | free(_xopt_ctx); \ 219 | break; \ 220 | } while (false) 221 | 222 | #ifdef __cplusplus 223 | } 224 | #endif 225 | #endif 226 | --------------------------------------------------------------------------------