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 size 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 size of bits or bytes.
26 : *
27 : * This includes a floating point number followed by a suffix such as "kB"
28 : * or "Gb" or "TiB".
29 : *
30 : * \note
31 : * The size can also represents bits, even though it was written to read
32 : * bytes. If your command line option is expecting a size in bits, this works
33 : * just as expected.
34 : *
35 : * The size conversions are based on the International System of Units (SI).
36 : *
37 : * See: https://en.wikipedia.org/wiki/Kilobyte
38 : */
39 :
40 : // self
41 : //
42 : #include "advgetopt/validator_size.h"
43 :
44 : #include "advgetopt/validator_double.h"
45 :
46 :
47 : // cppthread
48 : //
49 : #include <cppthread/log.h>
50 :
51 :
52 : // snapdev
53 : //
54 : #include <snapdev/int128_literal.h>
55 : #include <snapdev/math.h>
56 :
57 :
58 : // last include
59 : //
60 : #include <snapdev/poison.h>
61 :
62 :
63 :
64 :
65 : namespace advgetopt
66 : {
67 :
68 :
69 :
70 : namespace
71 : {
72 :
73 :
74 :
75 2 : class validator_size_factory
76 : : public validator_factory
77 : {
78 : public:
79 2 : validator_size_factory()
80 2 : {
81 2 : validator::register_validator(*this);
82 2 : }
83 :
84 4 : virtual std::string get_name() const override
85 : {
86 4 : return std::string("size");
87 : }
88 :
89 4 : virtual std::shared_ptr<validator> create(string_list_t const & data) const override
90 : {
91 4 : return std::make_shared<validator_size>(data);
92 : }
93 : };
94 :
95 2 : validator_size_factory g_validator_size_factory;
96 :
97 :
98 :
99 : } // no name namespace
100 :
101 :
102 :
103 :
104 :
105 : /** \brief Initialize the size validator.
106 : *
107 : * The constructor accepts a string defining the acceptable sizes.
108 : *
109 : * The string uses the following format:
110 : *
111 : * \code
112 : * start: flags
113 : * | start flags
114 : *
115 : * flags: 'si'
116 : * | 'legacy'
117 : * \endcode
118 : *
119 : * 'si' stands for "Systeme International" (French for International
120 : * System of Units); this means "1kB" will stand for "1000 bytes".
121 : *
122 : * 'legacy' means that one kilo bytes will be represented by 1024 bytes.
123 : * So "1kB" with the legacy flag turned on represents "1024 bytes".
124 : *
125 : * The 'si' and 'legacy' flags are exclusive, the last one will be effective.
126 : *
127 : * \param[in] flag_list The flags used to define how to interpret the data.
128 : */
129 4 : validator_size::validator_size(string_list_t const & flag_list)
130 : {
131 8 : for(auto r : flag_list)
132 : {
133 5 : if(r == "si")
134 : {
135 2 : f_flags &= ~VALIDATOR_SIZE_POWER_OF_TWO;
136 : }
137 4 : else if(r == "legacy")
138 : {
139 2 : f_flags |= VALIDATOR_SIZE_POWER_OF_TWO;
140 : }
141 : else
142 : {
143 2 : cppthread::log << cppthread::log_level_t::error
144 1 : << r
145 1 : << " is not a valid flag for the size validator."
146 2 : << cppthread::end;
147 1 : continue;
148 : }
149 : }
150 4 : }
151 :
152 :
153 : /** \brief Return the name of this validator.
154 : *
155 : * This function returns "size".
156 : *
157 : * \return "size".
158 : */
159 3 : std::string validator_size::name() const
160 : {
161 3 : return std::string("size");
162 : }
163 :
164 :
165 : /** \brief Determine whether value is a valid size.
166 : *
167 : * This function verifies that the specified value is a valid size.
168 : *
169 : * It makes sures that the value is a valid decimal number which optionally
170 : * starts with a sign (`[-+]?`) and is optionally followed by a known
171 : * measurement suffix.
172 : *
173 : * \param[in] value The value to validate.
174 : *
175 : * \return true if the value validates.
176 : */
177 : #pragma GCC diagnostic push
178 : #pragma GCC diagnostic ignored "-Wpedantic"
179 485591 : bool validator_size::validate(std::string const & value) const
180 : {
181 : using namespace snapdev::literals;
182 485591 : __int128 result(0_int128);
183 485591 : return convert_string(value, f_flags, result);
184 : }
185 : #pragma GCC diagnostic pop
186 :
187 :
188 : /** \brief Convert a string to a large integer (128 bits) value representing a size.
189 : *
190 : * This function is used to convert a string to a double representing a
191 : * size. The size can be specified with one of the following suffixes:
192 : *
193 : * * "B" -- 1000^0 bytes
194 : * * "kB" -- 1000^1 bytes
195 : * * "MB" -- 1000^2 bytes
196 : * * "GB" -- 1000^3 bytes
197 : * * "TB" -- 1000^4 bytes
198 : * * "PB" -- 1000^5 bytes
199 : * * "EB" -- 1000^6 bytes
200 : * * "ZB" -- 1000^7 bytes
201 : * * "YB" -- 1000^8 bytes
202 : * * "KiB" -- 1024^1 bytes
203 : * * "MiB" -- 1024^2 bytes
204 : * * "GiB" -- 1024^3 bytes
205 : * * "TiB" -- 1024^4 bytes
206 : * * "PiB" -- 1024^5 bytes
207 : * * "EiB" -- 1024^6 bytes
208 : * * "ZiB" -- 1024^7 bytes
209 : * * "YiB" -- 1024^8 bytes
210 : *
211 : * The suffix capitalization is not important since we can always distinguish
212 : * both types (power of 1000 or 1024). The 'B' represents bytes either way so
213 : * it does not need to be dinstinguished.
214 : *
215 : * In legacy mode (VALIDATOR_SIZE_POWER_OF_TWO flag set), the 1024 power
216 : * is always used.
217 : *
218 : * The final result is an integer representing bytes. If you use a decimal
219 : * number, it will be rounded down (floor). So "1.9B" returns 1. A decimal
220 : * number is practical for larger sizes such as "1.3GiB".
221 : *
222 : * \note
223 : * The result is returned in a 128 bit number because Zeta and Yeta values
224 : * do not fit in 64 bits.
225 : *
226 : * \todo
227 : * We may want to support full names instead of just the minimal abbreviated
228 : * suffixes (i.e. "3 bytes" fails). Also we could support bits but that is
229 : * \em complicated because the only difference is whether you use upper or
230 : * lower case characters (i.e. kB is kilo bytes, kb is kilo bits).
231 : *
232 : * \param[in] value The value to be converted to a size.
233 : * \param[in] flags The flags to determine how to interpret the suffix.
234 : * \param[out] result The resulting size in bits or bytes.
235 : *
236 : * \return true if the conversion succeeded.
237 : */
238 : #pragma GCC diagnostic push
239 : #pragma GCC diagnostic ignored "-Wpedantic"
240 809591 : bool validator_size::convert_string(
241 : std::string const & value
242 : , flag_t flags
243 : , __int128 & result)
244 : {
245 : using namespace snapdev::literals;
246 :
247 : // determine the factor by checking the suffix
248 : //
249 809591 : __int128 factor(1_int128);
250 809591 : std::string::size_type pos(value.length());
251 8545675 : for(; pos > 0; --pos)
252 : {
253 4677628 : char c(value[pos - 1]);
254 4677628 : if(c >= '0'
255 2653694 : && c <= '9'
256 3868042 : || c == '.')
257 : {
258 : break;
259 : }
260 : }
261 809591 : if(pos == 0)
262 : {
263 5 : return false;
264 : }
265 : __int128 const base(
266 809586 : (flags & VALIDATOR_SIZE_POWER_OF_TWO) != 0
267 809586 : ? 1024_int128
268 809586 : : 1000_int128);
269 1619172 : std::string const number(value.substr(0, pos));
270 2833516 : for(; pos < value.length() && isspace(value[pos]); ++pos);
271 809586 : if(pos < value.length())
272 : {
273 : // copy and force lowercase (although the case is important
274 : // when writing such a measurement, it does not matter when
275 : // testing here)
276 : //
277 1529207 : std::string suffix;
278 4452818 : for(; pos < value.length(); ++pos)
279 : {
280 3688208 : if(value[pos] >= 'A'
281 1844104 : && value[pos] <= 'Z')
282 : {
283 1439241 : suffix += value[pos] + 0x20;
284 : }
285 : else
286 : {
287 404863 : suffix += value[pos];
288 : }
289 : }
290 :
291 764610 : switch(suffix[0])
292 : {
293 44978 : case 'b':
294 44978 : if(suffix != "b")
295 : {
296 1 : return false;
297 : }
298 44977 : break;
299 :
300 89953 : case 'e':
301 89953 : if(suffix == "eb")
302 : {
303 44976 : factor = snapdev::pow(base, 6);
304 : }
305 44977 : else if(suffix == "eib")
306 : {
307 44976 : factor = snapdev::pow(1024_int128, 6);
308 : }
309 : else
310 : {
311 1 : return false;
312 : }
313 89952 : break;
314 :
315 89954 : case 'g':
316 89954 : if(suffix == "gb")
317 : {
318 44976 : factor = snapdev::pow(base, 3);
319 : }
320 44978 : else if(suffix == "gib")
321 : {
322 44977 : factor = snapdev::pow(1024_int128, 3);
323 : }
324 : else
325 : {
326 1 : return false;
327 : }
328 89953 : break;
329 :
330 89954 : case 'k':
331 89954 : if(suffix == "kb")
332 : {
333 44976 : factor = base;
334 : }
335 44978 : else if(suffix == "kib")
336 : {
337 44977 : factor = 1024_int128;
338 : }
339 : else
340 : {
341 1 : return false;
342 : }
343 89953 : break;
344 :
345 89955 : case 'm':
346 89955 : if(suffix == "mb")
347 : {
348 44976 : factor = snapdev::pow(base, 2);
349 : }
350 44979 : else if(suffix == "mib")
351 : {
352 44977 : factor = snapdev::pow(1024_int128, 2);
353 : }
354 : else
355 : {
356 2 : return false;
357 : }
358 89953 : break;
359 :
360 89954 : case 'p':
361 89954 : if(suffix == "pb")
362 : {
363 44976 : factor = snapdev::pow(base, 5);
364 : }
365 44978 : else if(suffix == "pib")
366 : {
367 44977 : factor = snapdev::pow(1024_int128, 5);
368 : }
369 : else
370 : {
371 1 : return false;
372 : }
373 89953 : break;
374 :
375 89954 : case 't':
376 89954 : if(suffix == "tb")
377 : {
378 44976 : factor = snapdev::pow(base, 4);
379 : }
380 44978 : else if(suffix == "tib")
381 : {
382 44976 : factor = snapdev::pow(1024_int128, 4);
383 : }
384 : else
385 : {
386 2 : return false;
387 : }
388 89952 : break;
389 :
390 89953 : case 'y':
391 89953 : if(suffix == "yb")
392 : {
393 44976 : factor = snapdev::pow(base, 8);
394 : }
395 44977 : else if(suffix == "yib")
396 : {
397 44976 : factor = snapdev::pow(1024_int128, 8);
398 : }
399 : else
400 : {
401 1 : return false;
402 : }
403 89952 : break;
404 :
405 89953 : case 'z':
406 89953 : if(suffix == "zb")
407 : {
408 44976 : factor = snapdev::pow(base, 7);
409 : }
410 44977 : else if(suffix == "zib")
411 : {
412 44976 : factor = snapdev::pow(1024_int128, 7);
413 : }
414 : else
415 : {
416 1 : return false;
417 : }
418 89952 : break;
419 :
420 2 : default:
421 2 : return false;
422 :
423 : }
424 : }
425 :
426 809573 : double base_number(0.0);
427 809573 : if(!validator_double::convert_string(number, base_number))
428 : {
429 5 : return false;
430 : }
431 :
432 : // TODO: I think that even with the long double this will lose bits
433 809568 : result = static_cast<long double>(base_number) * factor;
434 :
435 809568 : return true;
436 : }
437 : #pragma GCC diagnostic pop
438 :
439 :
440 :
441 6 : } // namespace advgetopt
442 : // vim: ts=4 sw=4 et
|