Line data Source code
1 : // Copyright (c) 2006-2022 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 lib
39 : //
40 : #include <cppthread/log.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 :
60 2 : class validator_duration_factory
61 : : public validator_factory
62 : {
63 : public:
64 2 : validator_duration_factory()
65 2 : {
66 2 : validator::register_validator(*this);
67 2 : }
68 :
69 4 : virtual std::string get_name() const override
70 : {
71 4 : return std::string("duration");
72 : }
73 :
74 3 : virtual std::shared_ptr<validator> create(string_list_t const & data) const override
75 : {
76 3 : return std::make_shared<validator_duration>(data);
77 : }
78 : };
79 :
80 2 : validator_duration_factory g_validator_duration_factory;
81 :
82 :
83 :
84 : } // no name namespace
85 :
86 :
87 :
88 :
89 :
90 : /** \brief Initialize the duration validator.
91 : *
92 : * The constructor accepts a string defining the acceptable durations.
93 : *
94 : * The string uses the following format:
95 : *
96 : * \code
97 : * start: flags
98 : * | start flags
99 : *
100 : * flags: 'small'
101 : * | 'large'
102 : * \endcode
103 : *
104 : * 'small' stands for small values (down to 1 second).
105 : *
106 : * 'large' stands for large values (so the 'm' suffix represents month,
107 : * not minutes).
108 : *
109 : * The 'small' and 'large' flags are exclusive, the last one will be effective.
110 : *
111 : * \param[in] flag_list The flags used to define the usage of the 'm' suffix.
112 : */
113 3 : validator_duration::validator_duration(string_list_t const & flag_list)
114 : {
115 5 : for(auto r : flag_list)
116 : {
117 2 : if(r == "small")
118 : {
119 1 : f_flags &= ~VALIDATOR_DURATION_LONG;
120 : }
121 1 : else if(r == "large")
122 : {
123 1 : f_flags |= VALIDATOR_DURATION_LONG;
124 : }
125 : else
126 : {
127 0 : cppthread::log << cppthread::log_level_t::error
128 0 : << r
129 0 : << " is not a valid flag for the duration validator."
130 0 : << cppthread::end;
131 0 : continue;
132 : }
133 : }
134 3 : }
135 :
136 :
137 : /** \brief Return the name of this validator.
138 : *
139 : * This function returns "duration".
140 : *
141 : * \return "duration".
142 : */
143 3 : std::string validator_duration::name() const
144 : {
145 3 : return std::string("duration");
146 : }
147 :
148 :
149 : /** \brief Determine whether value is a valid duration.
150 : *
151 : * This function verifies that the specified value is a valid duration.
152 : *
153 : * It makes sures that the value is a valid decimal number which optionally
154 : * starts with a sign (`[-+]?`) and is optionally followed by a known
155 : * measurement suffix.
156 : *
157 : * \param[in] value The value to validate.
158 : *
159 : * \return true if the value validates.
160 : */
161 567654 : bool validator_duration::validate(std::string const & value) const
162 : {
163 567654 : double result(0);
164 567654 : return convert_string(value, f_flags, result);
165 : }
166 :
167 :
168 : /** \brief Convert a string to a double value representing a duration.
169 : *
170 : * This function is used to convert a string to a double representing a
171 : * duration. The duration can be specified with one of the following suffixes:
172 : *
173 : * * "s" or " second" or " seconds" -- the double is returned as is
174 : * * "m" or " minute" or " minutes" -- the double is multiplied by 60
175 : * * "h" or " hour" or " hours" -- the double is multiplied by 3600
176 : * * "d" or " day" or " days" -- the double is multiplied by 86400
177 : * * "w" or " week" or " weeks" -- the double is multiplied by 604800
178 : * * "m" or " month" or " months" -- the double is multiplied by 2592000
179 : * * "y" or " year" or " years" -- the double is multiplied by 31536000
180 : *
181 : * The "m" suffix is interpreted as "minute" by default. The flags passed
182 : * to the contructor can change that interpretation into "month" instead.
183 : *
184 : * One month uses 30 days.
185 : *
186 : * One year uses 365 days.
187 : *
188 : * Note that the input can be a double. So you can define a duration of
189 : * "1.3 seconds" or "2.25 days".
190 : *
191 : * \todo
192 : * The last multiplication doesn't verify that no overflow or underflow
193 : * happens.
194 : *
195 : * \warning
196 : * There is no range checks in this function since it does not have access
197 : * to the ranges (i.e. it is static).
198 : *
199 : * \param[in] value The value to be converted to a duration.
200 : * \param[in] flags The flags to determine how to interpret the suffix.
201 : * \param[out] result The resulting duration in seconds.
202 : *
203 : * \return true if the conversion succeeded.
204 : */
205 945659 : bool validator_duration::convert_string(
206 : std::string const & value
207 : , flag_t flags
208 : , double & result)
209 : {
210 945659 : result = 0.0;
211 :
212 : // TODO: use libutf8 to walk the string and skip all UTF-8 spaces
213 : //
214 945659 : char const * s(value.c_str());
215 945659 : while(isspace(*s))
216 : {
217 0 : ++s;
218 : }
219 945659 : if(*s == '\0')
220 : {
221 3 : return false;
222 : }
223 :
224 945656 : double r(0.0);
225 2836938 : while(*s != '\0')
226 : {
227 : // get the number
228 : //
229 945662 : char const * number(s);
230 945662 : if(*s == '+'
231 756026 : || *s == '-')
232 : {
233 566382 : ++s;
234 : }
235 2836976 : while(*s >= '0' && *s <= '9')
236 : {
237 945657 : ++s;
238 : }
239 945662 : if(*s == '.')
240 : {
241 16077371 : do
242 : {
243 17023018 : ++s;
244 : }
245 17023018 : while(*s >= '0' && *s <= '9');
246 : }
247 945662 : double n(0.0);
248 2836986 : if(!validator_double::convert_string(
249 1891324 : (*number == '.' ? "0" : "") + std::string(number, s - number)
250 : , n))
251 : {
252 9 : return false;
253 : }
254 :
255 5673815 : while(isspace(*s))
256 : {
257 2364081 : ++s;
258 : }
259 :
260 : // get the suffix
261 : //
262 1891294 : std::string suffix;
263 3467370 : for(;; ++s)
264 : {
265 7880393 : if(*s >= 'A'
266 3467370 : && *s <= 'Z')
267 : {
268 : // make lowercase as we're at it
269 : //
270 0 : suffix += *s + 0x20;
271 : }
272 4413023 : else if(*s >= 'a'
273 3467370 : && *s <= 'z')
274 : {
275 3467370 : suffix += *s;
276 : }
277 : else
278 : {
279 : break;
280 : }
281 : }
282 :
283 945653 : double factor(1.0);
284 945653 : if(!suffix.empty()) // empty == "seconds"
285 : {
286 900621 : switch(suffix[0])
287 : {
288 135095 : case 'd':
289 270190 : if(suffix == "d"
290 90063 : || suffix == "day"
291 180128 : || suffix == "days")
292 : {
293 135092 : factor = 86400.0;
294 : }
295 : else
296 : {
297 3 : return false;
298 : }
299 135092 : break;
300 :
301 135092 : case 'h':
302 270184 : if(suffix == "h"
303 90060 : || suffix == "hour"
304 180122 : || suffix == "hours")
305 : {
306 135092 : factor = 3600.0;
307 : }
308 : else
309 : {
310 0 : return false;
311 : }
312 135092 : break;
313 :
314 225152 : case 'm':
315 225152 : if(suffix == "m")
316 : {
317 45032 : if((flags & VALIDATOR_DURATION_LONG) != 0)
318 : {
319 14922 : factor = 86400.0 * 30.0; // 1 month
320 : }
321 : else
322 : {
323 30110 : factor = 60.0; // 1 minute
324 : }
325 : }
326 360240 : else if(suffix == "minute"
327 180120 : || suffix == "minutes")
328 : {
329 90060 : factor = 60.0;
330 : }
331 180120 : else if(suffix == "month"
332 90060 : || suffix == "months")
333 : {
334 90060 : factor = 86400.0 * 30.0;
335 : }
336 : else
337 : {
338 0 : return false;
339 : }
340 225152 : break;
341 :
342 135093 : case 's':
343 270186 : if(suffix != "s"
344 90060 : && suffix != "second"
345 180123 : && suffix != "seconds")
346 : {
347 0 : return false;
348 : }
349 135093 : break;
350 :
351 135090 : case 'w':
352 270180 : if(suffix == "w"
353 90060 : || suffix == "week"
354 180120 : || suffix == "weeks")
355 : {
356 135090 : factor = 86400.0 * 7.0;
357 : }
358 : else
359 : {
360 0 : return false;
361 : }
362 135090 : break;
363 :
364 135090 : case 'y':
365 270180 : if(suffix == "y"
366 90060 : || suffix == "year"
367 180120 : || suffix == "years")
368 : {
369 135090 : factor = 86400.0 * 365.0;
370 : }
371 : else
372 : {
373 0 : return false;
374 : }
375 135090 : break;
376 :
377 9 : default:
378 9 : return false;
379 :
380 : }
381 : }
382 :
383 : // TODO: catch ERANGE errors
384 : //
385 945641 : r += n * factor;
386 :
387 945653 : while(isspace(*s))
388 : {
389 6 : ++s;
390 : }
391 : }
392 :
393 945635 : result = r;
394 :
395 945635 : return true;
396 : }
397 :
398 :
399 :
400 6 : } // namespace advgetopt
401 : // vim: ts=4 sw=4 et
|