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
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 2 : 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 2 : 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 8 : for(auto r : flag_list)
115 : {
116 5 : if(r == "small")
117 : {
118 2 : f_flags &= ~VALIDATOR_DURATION_LONG;
119 : }
120 4 : 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 : }
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 564748 : bool validator_duration::validate(std::string const & value) const
161 : {
162 564748 : double result(0);
163 564748 : 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 942754 : bool validator_duration::convert_string(
205 : std::string const & value
206 : , flag_t flags
207 : , double & result)
208 : {
209 942754 : result = 0.0;
210 :
211 : // TODO: use libutf8 to walk the string and skip all UTF-8 spaces
212 : //
213 942754 : char const * s(value.c_str());
214 942758 : while(isspace(*s))
215 : {
216 2 : ++s;
217 : }
218 942754 : if(*s == '\0')
219 : {
220 2 : return false;
221 : }
222 :
223 942752 : double r(0.0);
224 2828246 : while(*s != '\0')
225 : {
226 : // get the number
227 : //
228 942761 : char const * number(s);
229 942761 : if(*s == '+'
230 756027 : || *s == '-')
231 : {
232 569276 : ++s;
233 : }
234 2828295 : while(*s >= '0' && *s <= '9')
235 : {
236 942767 : ++s;
237 : }
238 942761 : if(*s == '.')
239 : {
240 16020674 : do
241 : {
242 16963422 : ++s;
243 : }
244 16963422 : while(*s >= '0' && *s <= '9');
245 942748 : 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 942760 : double n(0.0);
254 2828280 : if(!validator_double::convert_string(
255 1885520 : (*number == '.' ? "0" : "") + std::string(number, s - number)
256 : , n))
257 : {
258 3 : return false;
259 : }
260 :
261 5656433 : while(isspace(*s))
262 : {
263 2356838 : ++s;
264 : }
265 :
266 : // get the suffix
267 : //
268 1885504 : std::string suffix;
269 3456732 : for(;; ++s)
270 : {
271 7856221 : if(*s >= 'A'
272 3456732 : && *s <= 'Z')
273 : {
274 : // make lowercase as we're at it
275 : //
276 12 : suffix += *s + 0x20;
277 : }
278 4399477 : else if(*s >= 'a'
279 3456720 : && *s <= 'z')
280 : {
281 3456720 : suffix += *s;
282 : }
283 : else
284 : {
285 : break;
286 : }
287 : }
288 :
289 942757 : double factor(1.0);
290 942757 : if(!suffix.empty()) // empty == "seconds"
291 : {
292 897863 : switch(suffix[0])
293 : {
294 134680 : case 'd':
295 269360 : if(suffix == "d"
296 89785 : || suffix == "day"
297 179573 : || suffix == "days")
298 : {
299 134679 : factor = 86400.0;
300 : }
301 : else
302 : {
303 1 : return false;
304 : }
305 134679 : break;
306 :
307 134681 : case 'h':
308 269362 : if(suffix == "h"
309 89786 : || suffix == "hour"
310 179575 : || suffix == "hours")
311 : {
312 134679 : factor = 3600.0;
313 : }
314 : else
315 : {
316 2 : return false;
317 : }
318 134679 : break;
319 :
320 224464 : case 'm':
321 224464 : if(suffix == "m")
322 : {
323 44895 : if((flags & VALIDATOR_DURATION_LONG) != 0)
324 : {
325 15108 : factor = 86400.0 * 30.0; // 1 month
326 : }
327 : else
328 : {
329 29787 : factor = 60.0; // 1 minute
330 : }
331 : }
332 359138 : else if(suffix == "minute"
333 179569 : || suffix == "minutes")
334 : {
335 89784 : factor = 60.0;
336 : }
337 179570 : else if(suffix == "month"
338 89785 : || suffix == "months")
339 : {
340 89784 : factor = 86400.0 * 30.0;
341 : }
342 : else
343 : {
344 1 : return false;
345 : }
346 224463 : break;
347 :
348 134681 : case 's':
349 269362 : if(suffix != "s"
350 89785 : && suffix != "second"
351 179574 : && suffix != "seconds")
352 : {
353 1 : return false;
354 : }
355 134680 : break;
356 :
357 134677 : case 'w':
358 269354 : if(suffix == "w"
359 89785 : || suffix == "week"
360 179570 : || suffix == "weeks")
361 : {
362 134676 : factor = 86400.0 * 7.0;
363 : }
364 : else
365 : {
366 1 : return false;
367 : }
368 134676 : break;
369 :
370 134677 : case 'y':
371 269354 : if(suffix == "y"
372 89785 : || suffix == "year"
373 179570 : || suffix == "years")
374 : {
375 134676 : factor = 86400.0 * 365.0;
376 : }
377 : else
378 : {
379 1 : return false;
380 : }
381 134676 : break;
382 :
383 3 : default:
384 3 : return false;
385 :
386 : }
387 : }
388 :
389 : // TODO: catch ERANGE errors
390 : //
391 942747 : r += n * factor;
392 :
393 942765 : while(isspace(*s))
394 : {
395 9 : ++s;
396 : }
397 : }
398 :
399 942738 : result = r;
400 :
401 942738 : return true;
402 : }
403 :
404 :
405 :
406 6 : } // namespace advgetopt
407 : // vim: ts=4 sw=4 et
|