All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
KeyValuesData.h
Go to the documentation of this file.
1 /**
2  * @file icaruscode/Decode/DecoderTools/details/KeyValuesData.h
3  * @brief Simple parsed data format.
4  * @author Gianluca Petrillo (petrillo@slac.stanford.edu)
5  * @date May 9, 2021
6  *
7  * This library is header only.
8  */
9 
10 #ifndef ICARUSCODE_DECODE_DECODERTOOLS_DETAILS_KEYVALUESDATA_H
11 #define ICARUSCODE_DECODE_DECODERTOOLS_DETAILS_KEYVALUESDATA_H
12 
13 
14 // C++ standard libraries
15 #include <iosfwd> // std::ostream
16 #include <vector>
17 #include <string_view>
18 #include <string>
19 #include <optional>
20 #include <stdexcept> // std::runtime_error
21 #include <utility> // std::move()
22 #include <charconv> // std::from_chars()
23 #include <type_traits> // std::is_floating_point_v, std::enable_if_t
24 #include <cstddef> // std::size_t
25 
26 
27 // -----------------------------------------------------------------------------
28 namespace icarus {
29 
30  namespace details {
31  template <typename T, typename Enable = void>
33  template <typename T, unsigned int Base = 10U>
34  struct BaseWrapper;
35  }
36 
37  struct KeyValuesData;
38 
39  std::ostream& operator<< (std::ostream& out, KeyValuesData const& data);
40 
41 } // namespace icarus
42 
43 
44 // -----------------------------------------------------------------------------
45 /**
46  * @class icarus::KeyValuesData
47  * @brief Collection of items with key/values structure.
48  *
49  * This class collects `Item` objects, each being a string key and a sequence
50  * of zero or more values. An specific item can be accessed by its key
51  * (`findItem()`, `getItem()`) or all items may be iterated through (`items()`).
52  *
53  *
54  * Value type conversions
55  * -----------------------
56  *
57  * The `Item` objects in this class contain unparsed strings. Each `Item` has a
58  * key and a sequence of values. The values can be queried as strings or as
59  * other data types. Conversions are performed by
60  * `icarus::details::KeyValuesConverter`, which can be specialized with
61  * the needed conversion logic. Only one type of conversion to any given type
62  * is supported. Alternative conversions may be achieved using type wrappers
63  * (e.g. specializing for a `CaseInsensitive<S>` object that contains a string
64  * of type `S` and reading/converting into the string the result of the
65  * conversion).
66  *
67  * Each converter object specialization for a type `T` should support a call
68  * with argument `std::string` returning a `std::optional<T>`.
69  *
70  *
71  * Initialization and updates
72  * ---------------------------
73  *
74  * A `KeyValuesData` object always starts empty (implicit default constructor).
75  * A new item is also always created empty (`makeItem()`, `makeOrFetchItem()`)
76  * and with a set key.
77  *
78  * After an empty item (i.e. an item with a key but no values) is created,
79  * values can be added using the `Item` subclass interface (`addValue()`).
80  * Item values and keys can be modified by changing `Item` data members
81  * directly. The item to be modified is immediately returned by `makeItem()`;
82  * afterwards, an existing item may be still retrieved for changes with
83  * `makeOrFetchItem()` and `findItem()`.
84  *
85  * This object will refuse to create a new item with the same key as an existing
86  * one. However, the key of the item may be changed to any value after
87  * `makeItem()` is called, although the interface does not encourage that.
88  * If this introduces a duplicate key, the query functions will systematically
89  * retrieve only one of the items with the repeated key (which one and whether
90  * always the same one are undefined).
91  *
92  * Example:
93  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
94  * icarus::KeyValuesData data;
95  * data.makeItem("TriggerType").addValue("S5");
96  * data.makeItem("Triggers");
97  * data.makeItem("TriggerWindows").addValue("0C0B");
98  * data.makeItem("TPChits")
99  * .addValue("12").addValue("130").addValue("0").addValue("0");
100  * data.makeItem("TPChitTimes")
101  * .addValue("3").addValue("-1.1").addValue("-0.3").addValue("0.1");
102  * data.makeItem("PMThits").addValue("8");
103  * data.makeOrFetchItem("TPChits").addValue("115");
104  * data.makeOrFetchItem("TPChitTimes").addValue("-0.7");
105  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
106  * creates a `data` object with four items, a `TriggerType` one with one value,
107  * a `Triggers` one with no values, a `TriggerWindows` with a single value
108  * (expressed as a hexadecimal number), a `TPChits` one with four values,
109  * a `TPChitTimes` with four values (the first meant to be the number of
110  * remaining ones) and a `PMThits` with one. Finally, it adds one value to
111  * `TPChits` and one to `TPChitTimes`, which will both have five afterwards.
112  *
113  *
114  * Query
115  * ------
116  *
117  * The interface is quite terse.
118  *
119  * General query methods reports whether there is no item in the object
120  * (`empty()`) and how many items there are (`size()`).
121  *
122  * A item with a known key can be retrieved (`getItem()`, `findItem()`), or
123  * its existence may be tested (`hasItem()`).
124  *
125  * Finally, all items can be iterated (`items()`). In this case, the items
126  * are presented in the creation order.
127  *
128  * The `Item` interface is documented in that subclass.
129  *
130  * Example using the `data` object from the previous example:
131  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
132  * std::string triggerType = data.getItem("TriggerType").values()[0];
133  * std::vector<int> triggers = data.getItem("Triggers").getVector<int>();
134  * std::uint32_t triggerWindowBits
135  * = data.getItem("TriggerWindows").getNumber<std::uint32_t>(0, 16); // base 16
136  * std::vector<int> TPChits = data.getItem("TPChits").getVector<int>();
137  * std::vector<float> TPCtimes
138  * = data.getItem("TPChitTimes").getSizedVector<float>();
139  * std::vector<int> CRThits;
140  * if (auto const* item = data.findItem("CRThits"))
141  * CRThits = item->getVector<int>();
142  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
143  *
144  *
145  * Type conversion customization
146  * ------------------------------
147  *
148  * The values in a `Item` object can be queried as strings or as other data
149  * types using the `GetAs()` interface. Conversions are performed by
150  * `icarus::details::KeyValuesConverter`, which can be specialized with
151  * the needed conversion logic. Only one type of conversion to any given type
152  * is supported.
153  *
154  * Each converter object specialization for a type `T` should support a call
155  * with argument `std::string` returning a `std::optional<T>`.
156  *
157  */
159 
160  // --- BEGIN --- Exception definition ----------------------------------------
161  /// @name Exceptions
162  /// @{
163 
164  struct Error;
165  struct ErrorOnKey;
166  struct DuplicateKey;
167  struct ConversionFailed;
168  struct ItemNotFound;
169  struct ValueNotAvailable;
170  struct MissingSize;
171  struct WrongSize;
172 
173  /// @}
174  // --- END ----- Exception definition ----------------------------------------
175 
176  /**
177  * @brief Representation of a single item of data: a key and several values.
178  *
179  * Values can be added directly accessing the `values` data member, or
180  * with `addValue()` method call.
181  *
182  * Access to the values happens by index, with `nValues()` indices starting
183  * from `0` available. Direct access to `values()` is expected, and additional
184  * shortcuts are available:
185  * * `getAs()`, `getOptionalAs()` to convert to an arbitrary type; the
186  * standard conversion uses `from_chars()`, and specialization is
187  * possible
188  * * `getNumber()`, `getOptionalNumber()` to convert to a number
189  * * `getVector()` to convert to a vector of values (each like in `getAs()`)
190  * * `getSizedVector()` to convert to a vector of values; the first value is
191  * the size of the actual values.
192  */
193  struct Item: public std::pair<std::string, std::vector<std::string>> {
194 
195  using pair_t = std::pair<std::string, std::vector<std::string>>;
196 
197  /**
198  * @brief Alias for numerical base conversion.
199  * @tparam T type of the number to be converted
200  *
201  * Example of usage:
202  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
203  * auto const i16
204  * = item.getAs<int>(0U, icarus::KeyValuesData::Item::UseBase<int>{ 16 });
205  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
206  * converts the first value of `item` into an integer (`int`) with base 16.
207  */
208  template <typename T>
209  using UseBase
211 
212 
213  // --- BEGIN -- Constructors -----------------------------------------------
214 
215  /// Constructs a new item assigning it a key (which should not be changed).
216  Item(std::string key): pair_t{ std::move(key), {} } {}
217 
218  // --- END ---- Constructors -----------------------------------------------
219 
220 
221  // --- BEGIN -- Setting ----------------------------------------------------
222  /// @name Setting interface
223  /// @{
224 
225  //@{
226  /// Appends a string value to the list of values.
227  /// @return this same object (allows queueing calls in the same statement)
228  Item& addValue(std::string value)
229  { values().push_back(std::move(value)); return *this; }
230  Item& addValue(std::string_view value)
231  { return addValue(std::string{ value }); }
232  //@}
233 
234  /// Appends a sequence of values to this key.
235  template <typename BIter, typename EIter>
236  Item& addValues(BIter begin, EIter end)
237  { while (begin != end) addValue(*begin++); return *this; }
238 
239  //@{
240  /// Removes all the values.
241  void clear() { values().clear(); }
242  //@}
243 
244  /// @}
245  // --- END ---- Setting ----------------------------------------------------
246 
247 
248  // --- BEGIN -- Direct access ----------------------------------------------
249  /// @name Direct access to key and values
250  /// @{
251 
252  //@{
253  /// Returns the key of the item.
254  std::string const& key() const noexcept { return first; }
255  //@}
256 
257  //@{
258  /// Returns all item values, as strings.
259  std::vector<std::string>& values() noexcept { return second; }
260  std::vector<std::string> const& values() const noexcept { return second; }
261  //@}
262 
263  //@{
264  /// Returns the value at `index` (unchecked) with no conversion.
265  std::string const& value(std::size_t index = 0) const noexcept
266  { return values()[index]; }
267  //@}
268 
269  //@{
270  /// Returns the value at `index` with no conversion, no value if not present.
271  std::optional<std::string> optionalValue(std::size_t index) const noexcept;
272  //@}
273 
274  //@{
275 
276  /// Returns the number of values currently present.
277  std::size_t nValues() const noexcept { return values().size(); }
278 
279  /// @}
280  // --- END ---- Direct access ----------------------------------------------
281 
282 
283  // --- BEGIN -- Query ------------------------------------------------------
284  /// @name Query interface
285  /// @{
286 
287  //@{
288  /**
289  * @brief Returns the requested value, converted into type `T`
290  * @tparam T type to convert the value into
291  * @tparam Conv type of a functor for conversion of the value into type `T`
292  * @param index the index of the requested value
293  * @param converter a functor for conversion of the value into type `T`
294  * @return the requested value as an object of type `T`
295  * @throw ValueNotAvailable if no value is available with that index
296  * @throw ConversionFailed if the value could not be converted to type `T`
297  *
298  * Conversion is performed via `converter` object, functor taking a string
299  * and returning an object of type `std::optional<T>`. The functor can
300  * decline the conversion by returning an empty `std::optional`, or directly
301  * throw an exception on error.
302  */
303  template <typename T, typename Conv>
304  T getAs(std::size_t index, Conv converter) const;
305  //@}
306 
307  //@{
308  /**
309  * @brief Returns the requested value, converted into type `T`
310  * @tparam T type to convert the value into
311  * @param index the index of the requested value
312  * @return the requested value as an object of type `T`
313  * @throw ValueNotAvailable if no value is available with that index
314  * @throw ConversionFailed if the value could not be converted to type `T`
315  *
316  * Conversion is performed via an helper class
317  * `icarus::details::KeyValuesConverter` which can be specialized if needed,
318  * and that uses `from_chars()` for conversion.
319  */
320  template <typename T>
321  T getAs(std::size_t index) const;
322  //@}
323 
324 
325  //@{
326  /**
327  * @brief Returns the requested value, converted into type `T`
328  * @tparam T type to convert the value into
329  * @tparam IgnoreFormatErrors (default: `true`) how to treat conversion
330  * errors
331  * @tparam Conv type of a functor for conversion of the value into type `T`
332  * @param index the index of the requested value
333  * @param converter a functor for conversion of the value into type `T`
334  * @return the requested value, or an empty optional on failure
335  * @throw ConversionFailed if the value could not be converted to type `T`
336  *
337  * Conversion is performed via `converter` object, functor taking a string
338  * and returning an object of type `std::optional<T>`. The functor can
339  * decline the conversion by returning an empty `std::optional`, or directly
340  * throw an exception on error.
341  *
342  * If no value is available for the specified `index`, an empty optional
343  * is returned.
344  *
345  * An exception is thrown on conversion failures unless `IgnoreFormatErrors`
346  * is `true`, in which case an empty optional is also returned.
347  */
348  template <typename T, bool IgnoreFormatErrors = false, typename Conv>
349  std::optional<T> getOptionalAs(std::size_t index, Conv converter) const;
350  //@}
351 
352  //@{
353  /**
354  * @brief Returns the requested value, converted into type `T`
355  * @tparam T type to convert the value into
356  * @tparam IgnoreFormatErrors (default: `true`) how to treat conversion
357  * errors
358  * @param index the index of the requested value
359  * @param ignoreFormatErrors (default: `false`) ignore conversion errors
360  * @return the requested value, or an empty optional on failure
361  * @throw ConversionFailed if the value could not be converted to type `T`
362  *
363  * Conversion is performed via `converter` object, functor taking a string
364  * and returning an object of type `std::optional<T>`. The functor can
365  * decline the conversion by returning an empty `std::optional`, or directly
366  * throw an exception on error.
367  *
368  * If no value is available for the specified `index`, an empty optional
369  * is returned.
370  *
371  * An exception is thrown on conversion failures unless `IgnoreFormatErrors`
372  * is `true`, in which case an empty optional is also returned.
373  */
374  template <typename T, bool IgnoreFormatErrors = false>
375  std::optional<T> getOptionalAs(std::size_t index) const;
376  //@}
377 
378 
379  //@{
380  /**
381  * @brief Returns the requested value, converted into a number of type `T`
382  * @tparam T type of number to convert the value into
383  * @param index the index of the requested value
384  * @param base (default: `10`) numerical base of the input number
385  * @return the requested value as a number of type `T`
386  * @throw ValueNotAvailable if no value is available with that index
387  * @throw ConversionFailed if the value could not be converted to type `T`
388  *
389  * See `getAs()` for details.
390  *
391  * Note that the number must have no base prefix (e.g. `"F5"` for
392  * hexadecimal rather than `"0xF5"`).
393  */
394  template <typename T>
395  T getNumber(std::size_t index, unsigned int base) const;
396  template <typename T>
397  T getNumber(std::size_t index) const;
398  //@}
399 
400  // TODO do we need to propagate the IgnoreFormatErrors option?
401  //@{
402  /**
403  * @brief Returns the requested value, converted into a number of type `T`
404  * @tparam T type of number to convert the value into
405  * @param index the index of the requested value
406  * @param base (default: `10`) numerical base of the input number
407  * @return the requested value, or an empty optional on failure
408  * @throw ConversionFailed if the value could not be converted to type `T`
409  *
410  * See `getOptionalAs()` for details.
411  *
412  * Note that the number must have no base prefix (e.g. `"F5"` for
413  * hexadecimal rather than `"0xF5"`).
414  */
415  template <typename T>
416  std::optional<T> getOptionalNumber
417  (std::size_t index, unsigned int base) const;
418  template <typename T>
419  std::optional<T> getOptionalNumber(std::size_t index) const;
420  //@}
421 
422 
423  //@{
424  /**
425  * @brief Returns all the values, each converted into type `T`
426  * @tparam T type to convert the value into
427  * @tparam Conv type of a functor for conversion of the value into type `T`
428  * @param converter a functor for conversion of the value into type `T`
429  * @return a vector with all the converted values
430  * @throw ConversionFailed if any value could not be converted to type `T`
431  *
432  * Conversion of each element is performed by `getAs<T, Conv>()`.
433  *
434  * An exception is thrown on any conversion failure.
435  */
436  template <typename T, typename Conv = details::KeyValuesConverter<T>>
437  std::vector<T> getVector(Conv converter = {}) const;
438  //@}
439 
440  //@{
441  /**
442  * @brief Returns all the values, each converted into type `T`
443  * @tparam T type to convert the value into
444  * @tparam Conv type of a functor for conversion of the value into type `T`
445  * @param converter a functor for conversion of the value into type `T`
446  * @return a vector with all the converted values
447  * @throw MissingSize on any error converting the first value to a size
448  * @throw WrongSize if the actual number of values does not match the size
449  * @throw ConversionFailed if any value could not be converted to type `T`
450  *
451  * The first value (mandatory) is converted to represent the size of the
452  * vector. That is used as verification when converting all the other
453  * elements: if there is the wrong number of elements, an exception is
454  * thrown.
455  *
456  * Conversion of each element is performed by `getAs<T, Conv>()`.
457  *
458  * An exception is also thrown on conversion failure of any of the values.
459  */
460  template <typename T, typename Conv = details::KeyValuesConverter<T>>
461  std::vector<T> getSizedVector(Conv converter = Conv{}) const;
462  //@}
463 
464  /// @}
465  // --- END ---- Query ------------------------------------------------------
466 
467 
468  /// Lexicographic order by key (case-sensitive).
469  bool operator< (Item const& other) const noexcept
470  { return key() < other.key(); }
471 
472 
473  private:
474 
475  // implementation detail
476  template <typename T, typename Iter, typename Conv>
477  std::vector<T> convertVector(Iter begin, Iter end, Conv converter) const;
478 
479  /// Conversion functions.
480  template <typename T>
481  std::optional<T> convertStringInto(std::string const& valueStr) const
482  { return icarus::details::KeyValuesConverter<T>{}(valueStr); }
483 
484  }; // struct Item
485 
486 
487  // --- BEGIN -- Setter interface ---------------------------------------------
488  /// @name Setter interface
489  /// @{
490 
491  /// @brief Creates and registers a new item with the specified `key`.
492  /// @return the newly created item for modifications
493  Item& makeItem(std::string key);
494 
495  /// @brief Creates or retrieves an item with the specified `key`.
496  /// @return the newly created or existing item for modifications
497  Item& makeOrFetchItem(std::string const& key);
498 
499  /// Returns the item with specified `key`, `nullptr` if none.
500  Item* findItem(std::string const& key) noexcept;
501 
502  /// @}
503  // --- END ---- Setter interface ---------------------------------------------
504 
505 
506  // --- BEGIN -- Query interface ----------------------------------------------
507  /// @name Query interface
508  /// @{
509 
510  /// Returns the item with specified `key`, `nullptr` if none.
511  Item const* findItem(std::string const& key) const noexcept;
512 
513  /// Returns the item with specified `key`, throws `std::out_of_range` if none.
514  Item const& getItem(std::string const& key) const;
515 
516  /// Returns whether an item with the specified key is present.
517  bool hasItem(std::string const& key) const noexcept;
518 
519  /// Returns whether there is no item in data.
520  bool empty() const noexcept;
521 
522  /// Returns the number of items in the data.
523  std::size_t size() const noexcept;
524 
525  /// Returns a forward-iterable list of references to items.
526  decltype(auto) items() const noexcept;
527 
528  /// @}
529  // --- END ---- Query interface ----------------------------------------------
530 
531  private:
532 
533  std::vector<Item> fItems; ///< Collection of data items.
534 
535 
536  /// Creates, registers and return a new item (assumed not to exist yet).
537  Item& makeItemImpl(std::string key);
538 
539 }; // icarus::KeyValuesData
540 
541 
542 // -----------------------------------------------------------------------------
543 namespace icarus {
544  std::ostream& operator<<
545  (std::ostream& out, KeyValuesData::Item const& data);
546 } // namespace icarus
547 
548 
549 // -----------------------------------------------------------------------------
550 // --- Exception class definitions
551 // -----------------------------------------------------------------------------
552 struct icarus::KeyValuesData::Error: public std::runtime_error {
553 
554  Error(std::string msg): std::runtime_error(std::move(msg)) {}
555 
556 }; // icarus::KeyValuesData::Error()
557 
558 
559 // -----------------------------------------------------------------------------
561 
562  ErrorOnKey(std::string const& key, std::string const& msg)
563  : Error("Key '" + key + "': " + msg) {}
564 
565 }; // icarus::KeyValuesData::ErrorOnKey()
566 
567 
568 // -----------------------------------------------------------------------------
570 
571  DuplicateKey(std::string const& msg)
572  : Error("KeyValuesData::DuplicateKey: '" + msg + '\'')
573  {}
574 
575 }; // icarus::KeyValuesData::DuplicateKey()
576 
577 
578 // -----------------------------------------------------------------------------
580 
582  std::string const& key, std::string const& s, std::string const& tname = ""
583  )
584  : ErrorOnKey{ key,
585  "conversion of '" + s + "'"
586  + (tname.empty()? "": (" to type '" + tname + "'")) + " failed"
587  }
588  {}
589 
590  template <typename T>
591  static ConversionFailed makeFor(std::string const& key, std::string const& s)
592  { return { key, s, typeid(T).name() }; }
593 
594  template <typename T>
595  static ConversionFailed makeFor
596  (std::string const& key, std::size_t index, std::string const& s)
597  { return makeFor<T>(key + "[" + std::to_string(index) + "]", s); }
598 
599 }; // icarus::KeyValuesData::ConversionFailed()
600 
601 
602 // -----------------------------------------------------------------------------
604 
605  ItemNotFound(std::string const& key): ErrorOnKey(key, "key not found") {}
606 
607 }; // icarus::KeyValuesData::ItemNotFound()
608 
609 
610 // -----------------------------------------------------------------------------
612 
613  ValueNotAvailable(std::string const& key, std::size_t index)
614  : ErrorOnKey(key, "item value #" + std::to_string(index) + " not available")
615  {}
616 
617 }; // icarus::KeyValuesData::ValueNotAvailable()
618 
619 
620 // -----------------------------------------------------------------------------
622 
623  MissingSize(std::string const& key)
624  : ErrorOnKey
625  (key, "is required to have a size as first value, but it has no values")
626  {}
627 
628  MissingSize(std::string const& key, std::string const& valueStr)
629  : ErrorOnKey(
630  key,
631  " first value '" + valueStr + "' can't be converted into a vector size"
632  )
633  {}
634 
635 }; // icarus::KeyValuesData::MissingSize
636 
637 
638 // -----------------------------------------------------------------------------
640 
641  WrongSize(std::string const& key, std::size_t expected, std::size_t actual)
642  : ErrorOnKey(key,
643  std::to_string(expected) + " values (except the size) were expected, "
644  + std::to_string(actual) + " are present instead"
645  )
646  {}
647 
648 }; // icarus::KeyValuesData::WrongSize
649 
650 
651 // -----------------------------------------------------------------------------
652 // --- Template implementation
653 // -----------------------------------------------------------------------------
654 namespace icarus::details {
655 
656  template <typename T, unsigned int Base /* = 10U */>
657  struct BaseWrapper {
658  T value;
659  constexpr operator T() const noexcept { return value; }
660  }; // BaseWrapper
661 
662 } // namespace icarus::details
663 
664 
665 // -----------------------------------------------------------------------------
666 // --- icarus::KeyValuesData::Item
667 // -----------------------------------------------------------------------------
668 inline std::optional<std::string> icarus::KeyValuesData::Item::optionalValue
669  (std::size_t index) const noexcept
670 {
671  return (index < nValues())? std::optional{ values()[index] }: std::nullopt;
672 }
673 
674 
675 // -----------------------------------------------------------------------------
676 template <typename T, typename Conv>
678  (std::size_t index, Conv converter) const
679 {
680 
681  if (index >= values().size()) throw ValueNotAvailable(key(), index);
682 
683  auto const& valueStr = values()[index];
684  auto const number = converter(valueStr);
685  return number? *number: throw ConversionFailed::makeFor<T>(key(), valueStr);
686 
687 } // icarus::KeyValuesData::Item::getAs<>()
688 
689 
690 // -----------------------------------------------------------------------------
691 template <typename T>
692 T icarus::KeyValuesData::Item::getAs(std::size_t index) const
693  { return getAs<T>(index, details::KeyValuesConverter<T>{}); }
694 
695 
696 // -----------------------------------------------------------------------------
697 template <typename T, bool IgnoreFormatErrors /* = false */, typename Conv>
699  (std::size_t index, Conv converter) const
700 {
701  if (index < values().size()) return std::nullopt;
702 
703  auto const& valueStr = values()[index];
704  auto const number = converter(valueStr);
705  return (number || IgnoreFormatErrors)
706  ? number: throw ConversionFailed::makeFor<T>(key(), valueStr);
707 
708 } // icarus::KeyValuesData::Item::getOptionalAs()
709 
710 
711 // -----------------------------------------------------------------------------
712 template <typename T, bool IgnoreFormatErrors /* = false */>
714  (std::size_t index) const
715 {
716  return getOptionalAs<T, IgnoreFormatErrors>
718 }
719 
720 
721 // -----------------------------------------------------------------------------
722 template <typename T>
724  (std::size_t index, unsigned int base) const
725  { return getAs<T>(index, UseBase<T>{ base }); }
726 
727 
728 // -----------------------------------------------------------------------------
729 template <typename T>
730 T icarus::KeyValuesData::Item::getNumber(std::size_t index) const
731  { return getAs<T>(index, details::KeyValuesConverter<T>{}); }
732 
733 
734 // -----------------------------------------------------------------------------
735 template <typename T>
737  (std::size_t index, unsigned int base) const
738  { return getOptionalAs<T>(index, UseBase<T>{ base }); }
739 
740 
741 // -----------------------------------------------------------------------------
742 template <typename T>
744  (std::size_t index) const
745  { return getOptionalAs<T>(index, details::KeyValuesConverter<T>{}); }
746 
747 
748 // -----------------------------------------------------------------------------
749 template <typename T, typename Conv>
751  (Conv converter /* = {} */) const
752 {
753  return
754  convertVector<T>(values().begin(), values().end(), std::move(converter));
755 }
756 
757 
758 // -----------------------------------------------------------------------------
759 template <typename T, typename Conv>
761  (Conv converter /* = {} */) const
762 {
763 
764  if (values().empty()) throw MissingSize(key());
765 
766  std::size_t const n = getNumber<std::size_t>(0U);
767  if (n != values().size() - 1)
768  throw WrongSize(key(), n, values().size() - 1);
769 
770  return convertVector<T>
771  (std::next(values().begin()), values().end(), std::move(converter));
772 } // icarus::KeyValuesData::Item::getSizedVector()
773 
774 
775 // -----------------------------------------------------------------------------
776 template <typename T, typename Iter, typename Conv>
778  (Iter begin, Iter end, Conv converter) const
779 {
780  std::vector<T> data;
781  data.reserve(std::distance(begin, end));
782  Iter it = begin;
783  while (it != end) {
784  std::string const& valueStr = *it;
785  if (std::optional const number = converter(valueStr))
786  data.push_back(*number);
787  else {
788  throw ConversionFailed::makeFor<T>
789  (key(), std::distance(begin, it), valueStr);
790  }
791  ++it;
792  } // while
793  return data;
794 } // icarus::KeyValuesData::Item::convertVector()
795 
796 
797 // -----------------------------------------------------------------------------
798 inline decltype(auto) icarus::KeyValuesData::items() const noexcept
799  { return fItems; }
800 
801 
802 // -----------------------------------------------------------------------------
803 // --- icarus::details::KeyValuesConverter
804 // -----------------------------------------------------------------------------
805 template <typename T, typename /* = void */>
807  /*
808  * The "generic" converter supports arithmetic values and stuff that can be
809  * converted to string. For any other type and conversions, specialization
810  * is needed.
811  *
812  * NOTE: until we adopt C++17-compliant compilers (or better, libraries),
813  * also floating point numbers require specialization.
814  */
815 
816 
817  //@{
818  /// Convert a string `s` into a type `T`;
819  /// may return `std::nullopt` on "non-fatal" failure.
820  std::optional<T> operator() (std::string const& s) const
821  { return convert(s); }
822 
823  static std::optional<T> convert(std::string const& s)
824  {
825  if constexpr (std::is_arithmetic_v<T>) {
826  T number {}; // useless initialization to avoid GCC complains
827  char const *b = s.data(), *e = b + s.length();
828  return (std::from_chars(b, e, number).ptr == e)
829  ? std::make_optional(number): std::nullopt;
830  }
831  else if constexpr(std::is_constructible_v<T, std::string>){
832  return std::make_optional(T{ s });
833  }
834  else return std::nullopt;
835  } // convert()
836 
837  //@}
838 }; // icarus::details::KeyValuesConverter<>
839 
840 
841 /// Specialization for conversions with a numeric base.
842 template <typename T, unsigned int Base, typename Enable>
844  <icarus::details::BaseWrapper<T, Base>, Enable>
845 {
846  // NOTE: this specialization returns `T`, not BaseWrapper<T>.
847 
848  unsigned int base { Base }; ///< The numerical base this converter uses.
849 
850  //@{
851  /// Convert a string `s` into a numerical type `T` from the specified `base`;
852  /// may return `std::nullopt` on "non-fatal" failure.
853  std::optional<T> operator() (std::string const& s) const
854  { return convert(s); }
855 
856  std::optional<T> convert(std::string const& s) const
857  {
858  T number {}; // useless initialization to avoid GCC complains
859  char const *b = s.data(), *e = b + s.length();
860  return (std::from_chars(b, e, number, base).ptr == e)
861  ? std::make_optional(number): std::nullopt;
862  } // convert()
863 
864  //@}
865 }; // icarus::details::KeyValuesConverter<>
866 
867 
868 // --- BEGIN --- WORKAROUND --------------------------------------------------
869 /*
870  * Compilers like GCC 9.3.0 and Clang 7.0 are not fully C++17 compliant yet.
871  * `std::from_chars()` is not provided for floating points types.
872  * The first compiler versions supporting them are GCC 11.1 and Clang 12.0.1.
873  * I am providing a specialization to cover from that, until the compilers
874  * are updated.
875  */
876 #ifdef __GNUC__
877 # if (__GNUC__ >= 11)
878  // GCC 11 should support std::from_chars() for floating point types;
879  // remove this #if branch, and if Clang's is also already removed,
880  // remove the workaround too
881 # error "Redundant workaround on std::from_chars() for GCC"
882 # else
883 # define ICARUSCODE_DECODE_DECODERTOOLS_DETAILS_KEYVALUESDATA_NEEDS_FROMCHARS_FLOAT
884 # endif // __GNUC__
885 #endif // __GNUC__
886 
887 #ifdef __clang_major__
888 # if (__clang_major__ >= 12) || ((__clang_major__ == 12) && ((__clang_minor__ >= 1) || (__clang_patchlevel__ >= 1)))
889  // Clang 12.0.1 should support std::from_chars() for floating point types;
890  // remove this #if branch, and if GCC's is also already removed,
891  // remove the workaround too
892 # error "Redundant workaround on std::from_chars() for GCC"
893 # else
894 # define ICARUSCODE_DECODE_DECODERTOOLS_DETAILS_KEYVALUESDATA_NEEDS_FROMCHARS_FLOAT
895 # endif // __clang_major__
896 #endif // __clang_major__
897 
898 #ifdef ICARUSCODE_DECODE_DECODERTOOLS_DETAILS_KEYVALUESDATA_NEEDS_FROMCHARS_FLOAT
899 
900 #include <sstream>
901 
902 template <typename T>
904  <T, std::enable_if_t<std::is_floating_point_v<T>>>
905 {
906 
907  std::optional<T> operator() (std::string const& s) const
908  { return convert(s); }
909 
910  static std::optional<T> convert(std::string const& s)
911  {
912  T number {}; // useless initialization to avoid GCC complains
913  std::istringstream sstr{ s };
914  sstr >> number;
915  // check that no non-space character is left in the stream
916  return (sstr && (sstr >> std::ws).eof())
917  ? std::make_optional(number): std::nullopt;
918  } // convert()
919 
920 }; // icarus::details::KeyValuesConverter<floating point>
921 
922 #endif
923 // --- END ------ WORKAROUND --------------------------------------------------
924 
925 // -----------------------------------------------------------------------------
926 
927 #endif // ICARUSCODE_DECODE_DECODERTOOLS_DETAILS_KEYVALUESDATA_H
ItemNotFound(std::string const &key)
bool hasItem(std::string const &key) const noexcept
Returns whether an item with the specified key is present.
T getNumber(std::size_t index, unsigned int base) const
Returns the requested value, converted into a number of type T
double std(const std::vector< short > &wf, const double ped_mean, size_t start, size_t nsample)
Definition: UtilFunc.cxx:42
Item * findItem(std::string const &key) noexcept
Returns the item with specified key, nullptr if none.
std::pair< std::string, std::vector< std::string >> pair_t
decltype(auto) items() const noexcept
Returns a forward-iterable list of references to items.
Item const & getItem(std::string const &key) const
Returns the item with specified key, throws std::out_of_range if none.
std::optional< T > getOptionalNumber(std::size_t index, unsigned int base) const
Returns the requested value, converted into a number of type T
std::optional< T > getOptionalAs(std::size_t index, Conv converter) const
Returns the requested value, converted into type T
ErrorOnKey(std::string const &key, std::string const &msg)
std::optional< T > convertStringInto(std::string const &valueStr) const
Conversion functions.
Item & makeItemImpl(std::string key)
Creates, registers and return a new item (assumed not to exist yet).
Representation of a single item of data: a key and several values.
auto vector(Vector const &v)
Returns a manipulator which will print the specified array.
Definition: DumpUtils.h:265
std::ostream & operator<<(std::ostream &out, IntegerRanges< T, CheckGrowing > const &ranges)
double distance(geo::Point_t const &point, CathodeDesc_t const &cathode)
Returns the distance of a point from the cathode.
daq::details::BoardSetup_t convert(DaqDecoderICARUSPMT::BoardSetupConfig const &config)
Special function fhicl::TableAs uses to convert BoardSetupConfig.
MissingSize(std::string const &key)
Collection of items with key/values structure.
std::vector< T > convertVector(Iter begin, Iter end, Conv converter) const
std::size_t nValues() const noexcept
Returns the number of values currently present.
auto end(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:585
ValueNotAvailable(std::string const &key, std::size_t index)
std::vector< T > getVector(Conv converter={}) const
Returns all the values, each converted into type T
typename std::enable_if< B, T >::type enable_if_t
Definition: json.hpp:2191
string tname
Definition: names.py:3
T getAs(std::size_t index, Conv converter) const
Returns the requested value, converted into type T
std::vector< Item > fItems
Collection of data items.
Item(std::string key)
Constructs a new item assigning it a key (which should not be changed).
auto begin(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:573
std::string to_string(WindowPattern const &pattern)
then echo File list $list not found else cat $list while read file do echo $file sed s
Definition: file_to_url.sh:60
bool operator<(Item const &other) const noexcept
Lexicographic order by key (case-sensitive).
MissingSize(std::string const &key, std::string const &valueStr)
static ConversionFailed makeFor(std::string const &key, std::string const &s)
do i e
std::size_t size() const noexcept
Returns the number of items in the data.
WrongSize(std::string const &key, std::size_t expected, std::size_t actual)
then echo fcl name
std::vector< T > getSizedVector(Conv converter=Conv{}) const
Returns all the values, each converted into type T
temporary value
Item & makeItem(std::string key)
Creates and registers a new item with the specified key.
ConversionFailed(std::string const &key, std::string const &s, std::string const &tname="")
Item & makeOrFetchItem(std::string const &key)
Creates or retrieves an item with the specified key.
DuplicateKey(std::string const &msg)
static std::optional< T > convert(std::string const &s)
bool empty() const noexcept
Returns whether there is no item in data.