optparser.hpp (8292B)
1 // Copyright (c) 2020 Morel BĂ©renger 2 // 3 // This software is provided 'as-is', without any express or implied 4 // warranty. In no event will the authors be held liable for any damages 5 // arising from the use of this software. 6 // 7 // Permission is granted to anyone to use this software for any purpose, 8 // including commercial applications, and to alter it and redistribute it 9 // freely, subject to the following restrictions: 10 // 11 // 1. The origin of this software must not be misrepresented; you must not 12 // claim that you wrote the original software. If you use this software 13 // in a product, an acknowledgment in the product documentation would be 14 // appreciated but is not required. 15 // 2. Altered source versions must be plainly marked as such, and must not be 16 // misrepresented as being the original software. 17 // 3. This notice may not be removed or altered from any source distribution. 18 19 // this file contains the definitions of structures and 20 // functions to parse command-line options, with some C++ 21 // helpers (because templates rocks and C does not have 22 // them). 23 // C++ *should not* be required to use the core 24 // functionnality, but actual C uses have not been tested: 25 // you've been warned. 26 // Other functions are planned to parse data from sources 27 // different than argv, like getenv() or a configuration 28 // file. 29 // That's still on the TODO-list, though. 30 31 #ifndef OPTPARSER_HPP 32 #define OPTPARSER_HPP 33 34 #include <stdbool.h> 35 #include <stdint.h> 36 37 #ifdef __cplusplus 38 extern "C" { 39 #else 40 #define nullptr 0 41 #endif 42 43 #define STD_HELP { "help", "shows this message", 'h', 0, nullptr, nullptr, nullptr } 44 45 //TODO I wish this could be avoided, but FILE* is used there. 46 #include <stdio.h> 47 48 // this could probably be optimized, but considering it should not be used that 49 // often, I think it's better to just keep it as easy to use as possible. 50 // Some doc: 51 // * option: long name of the option, what people should use for readable shell 52 // code, but won't in direct calls, because it's faster to use a 53 // possible shorter version. 54 // On cmd line, prefixed with "--" and folowed by a "=" if option is 55 // not a flag. For example, the "foo" option's value would be set on 56 // cmd line with "--foo=value". 57 // Must be a null-byte terminated UTF-8 string. 58 // Character '=' is reserved and must not be used. 59 // * short_opt: character used as the short name. Short names are prefixed with 60 // "-" and directly followed by the value to affect to the option, 61 // for example: "-fvalue". 62 // * description: how the program should show describe the option to people that 63 // don't read the docs (hint: most of us). Default values should 64 // not be repeated in that text. 65 // Must be a null-byte terminated UTF-8 string. 66 // * value: a pointer to the variable to set, if applicable. If nullptr, 67 // occurrences will be counted. 68 // * set: how to set value given an UTF-8 null-terminated string. 69 // * show: how to render the current value. Should be used by an "help" option. 70 // * count: how many times the option was found. Can be used for various things, 71 // like exiting if --version, -v, --help or -h were found, or if a 72 // mandatory option is missing. 73 // If the option appears too often, the count will stop incrementing. 74 // TODO: implement a way to know which arguments were ignored (no match found) 75 // TODO: implement the "--" option to mean end of options (and return it's index?) 76 struct opt_desc_t 77 { 78 char const* option; 79 char const* description; 80 uint32_t short_option; 81 uint32_t count; 82 void* value; 83 bool (*set) ( void* val, char * arg ); 84 bool (*show)( void const* val, FILE_PTR target ); 85 }; 86 87 //see parse_error_msgs in implementation for descriptions 88 enum parse_error_t 89 { 90 NONE, 91 IGNORED, 92 MAX_COUNT, 93 SET_NO_VAL, 94 SET_VAL_IGN, 95 SET_FAIL, 96 BAD_ARGS, 97 BAD_SETTER, 98 }; 99 100 extern char const *parse_error_msgs[]; 101 #define arg_warning( arg, err ) fprintf( stderr, "Warning: in arg \"%s\" => %s(error code: %04x)\n", \ 102 arg, parse_error_msgs[err], err ) 103 #define arg_error( arg, err ) fprintf( stderr, "Error: in arg \"%s\" => %s(error code: %04x)\n", \ 104 arg, parse_error_msgs[err], err ) 105 106 // checks if a string is a command-line argument and processes it if yes 107 // return true if the argument was ignored, false if it was successfully 108 // processed. 109 // Range of checked options is: [start,end) 110 parse_error_t parse_cmd_opt( char* arg, opt_desc_t* start, opt_desc_t const* end ); 111 112 // prints a message describing options and their current value if applicable. 113 void print_opts( FILE_PTR target, opt_desc_t const* start, opt_desc_t const* end ); 114 115 #ifdef __cplusplus 116 } //end of extern "C" 117 118 // add some pretty generic and useful code for C++ users. 119 // C users will have to implement their owns wrappers (and maybe contribute it?). 120 // 121 // This list is wished to grow with time, but should never depend on things that 122 // have runtime cost (containers, exceptions, RTTI, etc). 123 // It is hoped that it's performance will be improved when possible. 124 125 // Depending on what you really use, you may have to define the following functions: 126 // * strncpy for static C string set() 127 // * fputs for C string show() 128 // * strtoull for unsigned integers show() 129 // * strtoll for signed integets show() 130 // * strtold for floating numbers show() 131 // * fprintf for all number related show() 132 133 // stdio.h is *not* included, to fasten build time and to let user use 134 // unlocked functions, which should be faster but not thread-safe. 135 136 #include <type_traits> 137 #include <limits> 138 139 #include "utils.hpp" 140 141 // C "semi-static" strings in pointer 142 template <typename T> 143 bool set( 144 typename std::enable_if <std::is_same<char const**,T>::value, void*>::type val, 145 char* arg ) 146 { 147 if( !val ) 148 { 149 return true; 150 } 151 152 *static_cast<char const**>( val ) = arg; 153 return false; 154 } 155 156 // C strings in statically allocated buffer 157 template <typename T, size_t SZ> 158 bool set( 159 typename std::enable_if <std::is_same<char*,T>::value, void*>::type val, 160 char* arg ) 161 { 162 if( !val ) 163 { 164 return true; 165 } 166 167 strncpy( static_cast<char*>( val ), arg, SZ - 1); 168 static_cast<char*>( val )[SZ-1] = 0; 169 return false; 170 } 171 172 template <typename T> 173 bool show( 174 typename std::enable_if <std::is_same<char*,T>::value, void const*>::type val, 175 FILE* target ) 176 { 177 if( !val ) 178 { 179 return true; 180 } 181 182 fputs( "(current value: \"", target ); 183 char const* current = *static_cast<char *const*>( val ); 184 current = current ? current : ""; 185 esc_fputs( current, target ); 186 fputs( "\")", target ); 187 return false; 188 } 189 190 // signed integrals 191 template <typename T> 192 bool set( 193 typename std::enable_if 194 < 195 std::is_integral<T>::value && std::is_signed<T>::value, 196 void* 197 >::type val, char* arg ) 198 { 199 if( !val ) 200 { 201 return true; 202 } 203 204 char* end; 205 auto v = strtoll( arg, &end, 0 ); 206 if( false 207 || errno 208 || 0 != *end 209 || std::numeric_limits<T>::min() > v 210 || std::numeric_limits<T>::max() < v ) 211 { 212 return true; 213 } 214 *static_cast<T*>( val ) = static_cast<T>( v ); 215 return false; 216 } 217 218 template <typename T> 219 bool show( 220 typename std::enable_if 221 < 222 std::is_integral<T>::value && std::is_signed<T>::value, 223 void const* 224 >::type val, FILE* target ) 225 { 226 if( !val ) 227 { 228 return true; 229 } 230 231 fprintf( target, "(current value: %d)", *static_cast<T const*>( val ) ); 232 return false; 233 } 234 235 // unsigned integrals 236 template <typename T> 237 bool set( 238 typename std::enable_if 239 < 240 std::is_integral<T>::value && std::is_unsigned<T>::value, 241 void* 242 >::type val, char* arg ) 243 { 244 if( !val ) 245 { 246 return true; 247 } 248 249 char* end; 250 auto v = strtoull( arg, &end, 0 ); 251 if( false 252 || errno 253 || 0 != *end 254 || std::numeric_limits<T>::min() > v 255 || std::numeric_limits<T>::max() < v ) 256 { 257 return true; 258 } 259 *static_cast<T*>( val ) = static_cast<T>( v ); 260 return false; 261 } 262 263 // floating numbers 264 template <typename T> 265 bool set( 266 typename std::enable_if 267 < 268 std::is_floating_point<T>::value, 269 void* 270 >::type val, char* arg ) 271 { 272 if( !val ) 273 { 274 return true; 275 } 276 277 char* end; 278 auto v = strtold( arg, &end ); 279 if( false 280 || errno 281 || 0 != *end 282 || std::numeric_limits<T>::min() > v 283 || std::numeric_limits<T>::max() < v ) 284 { 285 return true; 286 } 287 *static_cast<T*>( val ) = static_cast<T>( v ); 288 return false; 289 } 290 291 #endif 292 293 #endif