Line data Source code
1 : // Copyright (c) 2006-2024 Made to Order Software Corp. All Rights Reserved 2 : // 3 : // https://snapwebsites.org/project/advgetopt 4 : // contact@m2osw.com 5 : // 6 : // This program is free software; you can redistribute it and/or modify 7 : // it under the terms of the GNU General Public License as published by 8 : // the Free Software Foundation; either version 2 of the License, or 9 : // (at your option) any later version. 10 : // 11 : // This program is distributed in the hope that it will be useful, 12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 : // GNU General Public License for more details. 15 : // 16 : // You should have received a copy of the GNU General Public License along 17 : // with this program; if not, write to the Free Software Foundation, Inc., 18 : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 : 20 : /** \file 21 : * \brief Implementation of the duration validator. 22 : * 23 : * The advgetopt allows for validating the input parameters automatically. 24 : * This one validator checks whether the input represents what is considered 25 : * a valid duration. 26 : * 27 : * This includes a floating point number followed by a suffix such as "week" 28 : * or "days". 29 : */ 30 : 31 : // self 32 : // 33 : #include "advgetopt/validator_duration.h" 34 : 35 : #include "advgetopt/validator_double.h" 36 : 37 : 38 : // cppthread 39 : // 40 : #include <cppthread/log.h> 41 : 42 : 43 : // last include 44 : // 45 : #include <snapdev/poison.h> 46 : 47 : 48 : 49 : namespace advgetopt 50 : { 51 : 52 : 53 : 54 : namespace 55 : { 56 : 57 : 58 : 59 : class validator_duration_factory 60 : : public validator_factory 61 : { 62 : public: 63 2 : validator_duration_factory() 64 2 : { 65 2 : validator::register_validator(*this); 66 2 : } 67 : 68 4 : virtual std::string get_name() const override 69 : { 70 4 : return std::string("duration"); 71 : } 72 : 73 4 : virtual std::shared_ptr<validator> create(string_list_t const & data) const override 74 : { 75 4 : return std::make_shared<validator_duration>(data); 76 : } 77 : }; 78 : 79 : validator_duration_factory g_validator_duration_factory; 80 : 81 : 82 : 83 : } // no name namespace 84 : 85 : 86 : 87 : 88 : 89 : /** \brief Initialize the duration validator. 90 : * 91 : * The constructor accepts a string defining the acceptable durations. 92 : * 93 : * The string uses the following format: 94 : * 95 : * \code 96 : * start: flags 97 : * | start flags 98 : * 99 : * flags: 'small' 100 : * | 'large' 101 : * \endcode 102 : * 103 : * 'small' stands for small values (down to 1 second). 104 : * 105 : * 'large' stands for large values (so the 'm' suffix represents month, 106 : * not minutes). 107 : * 108 : * The 'small' and 'large' flags are exclusive, the last one will be effective. 109 : * 110 : * \param[in] flag_list The flags used to define the usage of the 'm' suffix. 111 : */ 112 4 : validator_duration::validator_duration(string_list_t const & flag_list) 113 : { 114 9 : for(auto r : flag_list) 115 : { 116 5 : if(r == "small") 117 : { 118 2 : f_flags &= ~VALIDATOR_DURATION_LONG; 119 : } 120 3 : else if(r == "large") 121 : { 122 2 : f_flags |= VALIDATOR_DURATION_LONG; 123 : } 124 : else 125 : { 126 2 : cppthread::log << cppthread::log_level_t::error 127 1 : << r 128 1 : << " is not a valid flag for the duration validator." 129 2 : << cppthread::end; 130 1 : continue; 131 : } 132 5 : } 133 4 : } 134 : 135 : 136 : /** \brief Return the name of this validator. 137 : * 138 : * This function returns "duration". 139 : * 140 : * \return "duration". 141 : */ 142 3 : std::string validator_duration::name() const 143 : { 144 3 : return std::string("duration"); 145 : } 146 : 147 : 148 : /** \brief Determine whether value is a valid duration. 149 : * 150 : * This function verifies that the specified value is a valid duration. 151 : * 152 : * It makes sures that the value is a valid decimal number which optionally 153 : * starts with a sign (`[-+]?`) and is optionally followed by a known 154 : * measurement suffix. 155 : * 156 : * \param[in] value The value to validate. 157 : * 158 : * \return true if the value validates. 159 : */ 160 561346 : bool validator_duration::validate(std::string const & value) const 161 : { 162 561346 : double result(0); 163 1122692 : return convert_string(value, f_flags, result); 164 : } 165 : 166 : 167 : /** \brief Convert a string to a double value representing a duration. 168 : * 169 : * This function is used to convert a string to a double representing a 170 : * duration. The duration can be specified with one of the following suffixes: 171 : * 172 : * * "s" or " second" or " seconds" -- the double is returned as is 173 : * * "m" or " minute" or " minutes" -- the double is multiplied by 60 174 : * * "h" or " hour" or " hours" -- the double is multiplied by 3600 175 : * * "d" or " day" or " days" -- the double is multiplied by 86400 176 : * * "w" or " week" or " weeks" -- the double is multiplied by 604800 177 : * * "m" or " month" or " months" -- the double is multiplied by 2592000 178 : * * "y" or " year" or " years" -- the double is multiplied by 31536000 179 : * 180 : * The "m" suffix is interpreted as "minute" by default. The flags passed 181 : * to the contructor can change that interpretation into "month" instead. 182 : * 183 : * One month uses 30 days. 184 : * 185 : * One year uses 365 days. 186 : * 187 : * Note that the input can be a double. So you can define a duration of 188 : * "1.3 seconds" or "2.25 days". 189 : * 190 : * \todo 191 : * The last multiplication doesn't verify that no overflow or underflow 192 : * happens. 193 : * 194 : * \warning 195 : * There is no range checks in this function since it does not have access 196 : * to the ranges (i.e. it is static). 197 : * 198 : * \param[in] value The value to be converted to a duration. 199 : * \param[in] flags The flags to determine how to interpret the suffix. 200 : * \param[out] result The resulting duration in seconds. 201 : * 202 : * \return true if the conversion succeeded. 203 : */ 204 939352 : bool validator_duration::convert_string( 205 : std::string const & value 206 : , flag_t flags 207 : , double & result) 208 : { 209 939352 : result = 0.0; 210 : 211 : // TODO: use libutf8 to walk the string and skip all UTF-8 spaces 212 : // 213 939352 : char const * s(value.c_str()); 214 939354 : while(isspace(*s)) 215 : { 216 2 : ++s; 217 : } 218 939352 : if(*s == '\0') 219 : { 220 2 : return false; 221 : } 222 : 223 939350 : double r(0.0); 224 1878695 : while(*s != '\0') 225 : { 226 : // get the number 227 : // 228 939359 : char const * number(s); 229 939359 : if(*s == '+' 230 756027 : || *s == '-') 231 : { 232 572678 : ++s; 233 : } 234 1878724 : while(*s >= '0' && *s <= '9') 235 : { 236 939365 : ++s; 237 : } 238 939359 : if(*s == '.') 239 : { 240 : do 241 : { 242 16917432 : ++s; 243 : } 244 16917432 : while(*s >= '0' && *s <= '9'); 245 939346 : if(*s == '.') 246 : { 247 : // this would otherwise allow "2..8 year" which is probably 248 : // a mistake (i.e. not "2.s" and ".8 year") 249 : // 250 1 : return false; 251 : } 252 : } 253 939358 : double n(0.0); 254 : #pragma GCC diagnostic push 255 : #pragma GCC diagnostic ignored "-Wrestrict" 256 2818074 : if(!validator_double::convert_string( 257 1878716 : (*number == '.' ? "0" : "") + std::string(number, s - number) 258 : , n)) 259 : { 260 3 : return false; 261 : } 262 : #pragma GCC diagnostic pop 263 : 264 3287688 : while(isspace(*s)) 265 : { 266 2348333 : ++s; 267 : } 268 : 269 : // get the suffix 270 : // 271 939355 : std::string suffix; 272 3444258 : for(;; ++s) 273 : { 274 4383613 : if(*s >= 'A' 275 3444258 : && *s <= 'Z') 276 : { 277 : // make lowercase as we're at it 278 : // 279 12 : suffix += *s + 0x20; 280 : } 281 4383601 : else if(*s >= 'a' 282 3444246 : && *s <= 'z') 283 : { 284 3444246 : suffix += *s; 285 : } 286 : else 287 : { 288 : break; 289 : } 290 : } 291 : 292 939355 : double factor(1.0); 293 939355 : if(!suffix.empty()) // empty == "seconds" 294 : { 295 894623 : switch(suffix[0]) 296 : { 297 134194 : case 'd': 298 134194 : if(suffix == "d" 299 89461 : || suffix == "day" 300 223655 : || suffix == "days") 301 : { 302 134193 : factor = 86400.0; 303 : } 304 : else 305 : { 306 1 : return false; 307 : } 308 134193 : break; 309 : 310 134195 : case 'h': 311 134195 : if(suffix == "h" 312 89462 : || suffix == "hour" 313 223657 : || suffix == "hours") 314 : { 315 134193 : factor = 3600.0; 316 : } 317 : else 318 : { 319 2 : return false; 320 : } 321 134193 : break; 322 : 323 223654 : case 'm': 324 223654 : if(suffix == "m") 325 : { 326 44733 : if((flags & VALIDATOR_DURATION_LONG) != 0) 327 : { 328 15066 : factor = 86400.0 * 30.0; // 1 month 329 : } 330 : else 331 : { 332 29667 : factor = 60.0; // 1 minute 333 : } 334 : } 335 178921 : else if(suffix == "minute" 336 178921 : || suffix == "minutes") 337 : { 338 89460 : factor = 60.0; 339 : } 340 89461 : else if(suffix == "month" 341 89461 : || suffix == "months") 342 : { 343 89460 : factor = 86400.0 * 30.0; 344 : } 345 : else 346 : { 347 1 : return false; 348 : } 349 223653 : break; 350 : 351 134195 : case 's': 352 134195 : if(suffix != "s" 353 89461 : && suffix != "second" 354 223656 : && suffix != "seconds") 355 : { 356 1 : return false; 357 : } 358 134194 : break; 359 : 360 134191 : case 'w': 361 134191 : if(suffix == "w" 362 89461 : || suffix == "week" 363 223652 : || suffix == "weeks") 364 : { 365 134190 : factor = 86400.0 * 7.0; 366 : } 367 : else 368 : { 369 1 : return false; 370 : } 371 134190 : break; 372 : 373 134191 : case 'y': 374 134191 : if(suffix == "y" 375 89461 : || suffix == "year" 376 223652 : || suffix == "years") 377 : { 378 134190 : factor = 86400.0 * 365.0; 379 : } 380 : else 381 : { 382 1 : return false; 383 : } 384 134190 : break; 385 : 386 3 : default: 387 3 : return false; 388 : 389 : } 390 : } 391 : 392 : // TODO: catch ERANGE errors 393 : // 394 939345 : r += n * factor; 395 : 396 939354 : while(isspace(*s)) 397 : { 398 9 : ++s; 399 : } 400 939355 : } 401 : 402 939336 : result = r; 403 : 404 939336 : return true; 405 : } 406 : 407 : 408 : 409 : } // namespace advgetopt 410 : // vim: ts=4 sw=4 et