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 double validator. 22 : * 23 : * This validator can be used to convert the value of a parameter to a 24 : * double with overflow and underflow verifications. 25 : */ 26 : 27 : // self 28 : // 29 : #include "advgetopt/validator_double.h" 30 : 31 : 32 : // cppthread 33 : // 34 : #include <cppthread/log.h> 35 : 36 : 37 : // snapdev 38 : // 39 : #include <snapdev/not_used.h> 40 : #include <snapdev/trim_string.h> 41 : 42 : 43 : // last include 44 : // 45 : #include <snapdev/poison.h> 46 : 47 : 48 : 49 : 50 : namespace advgetopt 51 : { 52 : 53 : 54 : 55 : namespace 56 : { 57 : 58 : 59 : class validator_double_factory 60 : : public validator_factory 61 : { 62 : public: 63 2 : validator_double_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("double"); 71 : } 72 : 73 42 : virtual std::shared_ptr<validator> create(string_list_t const & data) const override 74 : { 75 42 : snapdev::NOT_USED(data); // ignore `data` 76 42 : return std::make_shared<validator_double>(data); 77 : } 78 : }; 79 : 80 : validator_double_factory g_validator_double_factory; 81 : 82 : 83 : } // no name namespace 84 : 85 : 86 : 87 : 88 : /** \brief Initialize the double validator. 89 : * 90 : * The constructor accepts a string with values and ranges which are 91 : * used to limit the values that can be used with this parameter. 92 : * 93 : * Remember that the default does not have to be included in these 94 : * values. It will still be viewed as \em valid. 95 : * 96 : * The string uses the following format: 97 : * 98 : * \code 99 : * start: range 100 : * | start ',' range 101 : * 102 : * range: number 103 : * | number '...' number 104 : * 105 : * number: [-+]?[0-9]+(.[0-9]+([eE][+-][0-9]+)?)? 106 : * \endcode 107 : * 108 : * Note that a single number is considered to be a range and is managed 109 : * the exact same way. A value which matches any of the ranges is considered 110 : * valid. 111 : * 112 : * The start and end values of a range are optional. If not specified, the 113 : * start value is set to the minimum double value. If the not specified, 114 : * the end value is set to the maximum double value. 115 : * 116 : * Example: 117 : * 118 : * \code 119 : * "-10.01...+10.05,0.0005661" 120 : * \endcode 121 : * 122 : * This example allows all values between -10.01 and +10.05 inclusive and also 123 : * allows the value 0.0005661. 124 : * 125 : * \code 126 : * "0.0..." 127 : * \endcode 128 : * 129 : * This example allows all positive values and zero. 130 : * 131 : * \param[in] ranges The ranges used to limit the double. 132 : */ 133 42 : validator_double::validator_double(string_list_t const & range_list) 134 : { 135 42 : range_t range; 136 260 : for(auto r : range_list) 137 : { 138 218 : std::string::size_type const pos(r.find("...")); 139 218 : if(pos == std::string::npos) 140 : { 141 195 : if(!convert_string(r, range.f_minimum)) 142 : { 143 2 : cppthread::log << cppthread::log_level_t::error 144 1 : << r 145 : << " is not a valid standalone value;" 146 : " it must be a valid floating point," 147 1 : " optionally preceeded by a sign (+ or -)." 148 2 : << cppthread::end; 149 1 : continue; 150 : } 151 194 : range.f_maximum = range.f_minimum; 152 : } 153 : else 154 : { 155 69 : std::string const min_value(snapdev::trim_string(r.substr(0, pos))); 156 23 : if(!min_value.empty()) 157 : { 158 23 : if(!convert_string(min_value, range.f_minimum)) 159 : { 160 2 : cppthread::log << cppthread::log_level_t::error 161 1 : << min_value 162 : << " is not a valid value for your range's start;" 163 : " it must be a valid floating point," 164 1 : " optionally preceeded by a sign (+ or -)." 165 2 : << cppthread::end; 166 1 : continue; 167 : } 168 : } 169 : 170 66 : std::string const max_value(snapdev::trim_string(r.substr(pos + 3))); 171 22 : if(!max_value.empty()) 172 : { 173 22 : if(!convert_string(max_value, range.f_maximum)) 174 : { 175 2 : cppthread::log << cppthread::log_level_t::error 176 1 : << max_value 177 : << " is not a valid value for your range's end;" 178 : " it must be a valid floating point," 179 1 : " optionally preceeded by a sign (+ or -)." 180 2 : << cppthread::end; 181 1 : continue; 182 : } 183 : } 184 : 185 21 : if(range.f_minimum > range.f_maximum) 186 : { 187 2 : cppthread::log << cppthread::log_level_t::error 188 1 : << min_value 189 1 : << " has to be smaller or equal to " 190 1 : << max_value 191 1 : << "; you have an invalid range." 192 2 : << cppthread::end; 193 1 : continue; 194 : } 195 25 : } 196 214 : f_allowed_values.push_back(range); 197 218 : } 198 42 : } 199 : 200 : 201 : /** \brief Return the name of this validator. 202 : * 203 : * This function returns "double". 204 : * 205 : * \return "double". 206 : */ 207 41 : std::string validator_double::name() const 208 : { 209 41 : return std::string("double"); 210 : } 211 : 212 : 213 : /** \brief Determine whether value is a double. 214 : * 215 : * This function verifies that the specified value is a valid double. 216 : * 217 : * It makes sures that the value is only composed of digits (`[0-9]+`) 218 : * and optionally has a decimal pointer followed by more digits and 219 : * an optional exponent. 220 : * 221 : * The number may also start with a sign (`[-+]?`). 222 : * 223 : * If ranges were defined, then the function also verifies that the 224 : * value is within at least one of the ranges. 225 : * 226 : * \param[in] value The value to validate. 227 : * 228 : * \return true if the value validates. 229 : */ 230 135466 : bool validator_double::validate(std::string const & value) const 231 : { 232 135466 : double result(0.0); 233 135466 : if(convert_string(value, result)) 234 : { 235 51462 : if(f_allowed_values.empty()) 236 : { 237 1479 : return true; 238 : } 239 : 240 265686 : for(auto f : f_allowed_values) 241 : { 242 225526 : if(result >= f.f_minimum 243 121614 : && result <= f.f_maximum) 244 : { 245 9823 : return true; 246 : } 247 : } 248 40160 : return false; 249 : } 250 : 251 84004 : return false; 252 : } 253 : 254 : 255 : /** \brief Convert a string to a double value. 256 : * 257 : * This function is used to convert a string to a double with full 258 : * boundary verification. 259 : * 260 : * \todo 261 : * This function calls std::strtod() which interprets the decimal separator 262 : * depending on the current locale. We really want to only accept periods. 263 : * 264 : * \warning 265 : * There is no range checks in this function since it does not have access 266 : * to the ranges (i.e. it is static). 267 : * 268 : * \param[in] value The value to be converted to a double. 269 : * \param[out] result The resulting double. 270 : * 271 : * \return true if the conversion succeeded. 272 : */ 273 2063392 : bool validator_double::convert_string(std::string const & value, double & result) 274 : { 275 2063392 : char const * start(value.c_str()); 276 : 277 : // do not allow spaces before the number 278 : // 279 2063392 : if(start[0] != '+' 280 1673501 : && start[0] != '-' 281 847219 : && (start[0] < '0' || start[0] > '9')) 282 : { 283 42008 : return false; 284 : } 285 : 286 2021384 : char * end(nullptr); 287 2021384 : errno = 0; 288 2021384 : result = std::strtod(start, &end); 289 : 290 : // do not allow anything after the last digit 291 : // also return false on an overflow 292 : // 293 2021384 : return end == start + value.length() 294 2021384 : && errno != ERANGE; 295 : } 296 : 297 : 298 : 299 : } // namespace advgetopt 300 : // vim: ts=4 sw=4 et