ampsci
High-precision calculations for one- and two-valence atomic systems
InputBlock.hpp
1#pragma once
2#include "fmt/color.hpp"
3#include "qip/String.hpp" //for case insensitive
4#include <algorithm>
5#include <array>
6#include <chrono>
7#include <fstream>
8#include <iostream>
9#include <istream>
10#include <optional>
11#include <sstream>
12#include <string>
13#include <string_view>
14#include <vector>
15
16namespace IO {
17
18//! Prints unkown option warning, with suggested alternative
19inline void unkown_option(std::string_view test_string,
20 const std::vector<std::string> &list) {
21 fmt2::warning();
22 std::cout << ": Unknown option: " << test_string << "\n";
23 if (!list.empty())
24 std::cout << "Did you mean: " << qip::ci_closest_match(test_string, list)
25 << " ?\n ";
26}
27
28//==============================================================================
29//! Removes all white space (space, tab, newline), except for those in quotes
30inline std::string removeSpaces(std::string str);
31
32//! Removes all quote marks
33inline std::string removeQuoteMarks(std::string str);
34
35//! Removes all c++ style block comments from a string
36inline void removeBlockComments(std::string &input);
37
38//! Removes all c++ style comments from a string (block and line)
39inline std::string removeComments(const std::string &input);
40
41//! Expands "#include" files
42inline std::string expandIncludes(std::string input);
43
44//! Parses a string to type T by stringstream
45template <typename T>
46inline T parse_str_to_T(const std::string &value_as_str);
47
48//! Parses entire file into string. Note: v. inefficient
49inline std::string file_to_string(const std::istream &file);
50
51//! Compile-time trait: IsVector<T>::v is true if T is std::vector. IsVector<T>::t is the element type.
52template <typename T>
53struct IsVector {
54 constexpr static bool v = false;
55 using t = T;
56};
57template <typename T>
58struct IsVector<std::vector<T>> {
59 constexpr static bool v = true;
60 using t = T;
61};
62
63//! Compile-time trait: IsArray<T>::v is true if T is std::array. IsArray<T>::t is the element type; IsArray<T>::size is the extent.
64template <typename T>
65struct IsArray {
66 constexpr static bool v = false;
67 using t = T;
68 static constexpr std::size_t size = 0;
69};
70template <typename T, std::size_t N>
71struct IsArray<std::array<T, N>> {
72 constexpr static bool v = true;
73 using t = T;
74 static constexpr std::size_t size = N;
75};
76
77//! Prints a line of 'c' characters (dflt '*'), num chars long (dflt 80) to
78//! cout
79inline void print_line(const char c = '*', const int num = 80) {
80 for (int i = 0; i < num; i++)
81 std::cout << c;
82 std::cout << "\n";
83}
84
85//==============================================================================
86//! Returns current local date and time as a string, e.g. "2026-05-14 13:01:02".
87inline std::string time_date() {
88 const auto now =
89 std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
90 char buffer[30];
91 std::strftime(buffer, 30, "%F %T", localtime(&now));
92 return buffer;
93}
94//! Returns current local date as a string, e.g. "2026-05-14".
95inline std::string date() {
96 const auto now =
97 std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
98 char buffer[30];
99 std::strftime(buffer, 30, "%F", localtime(&now));
100 return buffer;
101}
102//! Returns current local time as a string, e.g. "13:01:02".
103inline std::string time() {
104 const auto now =
105 std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
106 char buffer[30];
107 std::strftime(buffer, 30, "%T", localtime(&now));
108 return buffer;
109}
110
111//==============================================================================
112//! Simple struct; holds key-value pair, both strings. == compares key
113struct Option {
114 std::string key;
115 std::string value_str;
116
117 friend bool operator==(Option option, std::string_view tkey) {
118 // return option.key == tkey;
119 return qip::ci_wc_compare(tkey, option.key);
120 }
121 friend bool operator==(std::string_view tkey, Option option) {
122 return option == tkey;
123 }
124 friend bool operator!=(Option option, std::string_view tkey) {
125 return !(option == tkey);
126 }
127 friend bool operator!=(std::string_view tkey, Option option) {
128 return !(option == tkey);
129 }
130};
131
132//==============================================================================
133/*!
134 @brief Holds a named list of key=value options and nested InputBlocks.
135 @details
136 Parses and stores structured input in the format:
137
138 ```
139 BlockName1 {
140 option1 = value1;
141 option2 = value2;
142 InnerBlock {
143 option1 = v3;
144 }
145 }
146 ```
147
148 Can be constructed from a literal option list, a string, or a file stream.
149 Block and option name comparison is case-insensitive.
150
151 @note Implementations are inline in the header for single-file header-only use.
152*/
153// nb: implementations are separated below (inline) for readability only
155private:
156 std::string m_name{};
157 std::vector<Option> m_options{};
158 std::vector<InputBlock> m_blocks{};
159
160public:
161 //! Default constructor; name will be blank.
163
164 //! Constructs from a literal list of Option structs.
165 InputBlock(std::string_view name, std::initializer_list<Option> options = {})
166 : m_name(name), m_options(options) {}
167
168 //! Constructs by parsing @p string_input in Block{option=value;} format.
169 InputBlock(std::string_view name, const std::string &string_input)
170 : m_name(name) {
171 add(string_input);
172 }
173
174 //! Constructs by reading and parsing a plain-text file stream.
175 InputBlock(std::string_view name, const std::istream &file) : m_name(name) {
176 add(file_to_string(file));
177 }
178
179 /*!
180 @brief Appends or merges a child InputBlock.
181 @details
182 If @p merge is false (default), always appends @p block.
183 If @p merge is true and a block with the same name already exists,
184 the options from @p block are merged into the existing block instead.
185 */
186 inline void add(InputBlock block, bool merge = false);
187
188 //! Merges @p block into an existing block of the same name, or appends it.
189 inline void merge(InputBlock block) { add(block, true); }
190
191 //! Appends a single Option to the option list.
192 inline void add(Option option);
193
194 //! Appends a list of Options to the option list.
195 inline void add(const std::vector<Option> &options);
196
197 /*!
198 @brief Parses @p string and adds its options and blocks.
199 @details
200 Comments, whitespace, and quote marks are stripped before parsing.
201 If @p merge is true, duplicate block names are consolidated rather than
202 appended.
203 */
204 inline void add(const std::string &string, bool merge = false);
205
206 //! Parses @p string and merges any duplicate block names.
207 inline void merge(const std::string &string) { add(string, true); }
208
209 //! Returns the name of this block.
210 std::string_view name() const { return m_name; }
211
212 //! Returns const reference to the list of options.
213 const std::vector<Option> &options() const { return m_options; }
214
215 //! Returns const reference to the list of child blocks.
216 const std::vector<InputBlock> &blocks() const { return m_blocks; }
217
218 //! Equality/inequality compare by block name (case-insensitive).
219 friend inline bool operator==(InputBlock block, std::string_view name);
220 friend inline bool operator==(std::string_view name, InputBlock block);
221 friend inline bool operator!=(InputBlock block, std::string_view name);
222 friend inline bool operator!=(std::string_view name, InputBlock block);
223
224 /*!
225 @brief Returns the value of @p key, or @p default_value if not found.
226 @details
227 If the same key appears more than once, the later occurrence takes
228 precedence. For bool, accepts "true"/"yes"/"y" (case-insensitive).
229 For std::vector<T> or std::array<T,N>, parses a comma-separated list.
230 @note Cannot be used with T = const char*; use std::string instead.
231 */
232 template <typename T>
233 T get(std::string_view key, T default_value) const;
234
235 /*!
236 @brief Returns an optional value for @p key; empty if not found or unset.
237 @details
238 If the same key appears more than once, the later occurrence takes
239 precedence. A value of "default" or empty string is treated as unset
240 (returns nullopt). Supports T = std::vector<T> or std::array<T,N> for
241 comma-separated list values.
242 */
243 template <typename T = std::string>
244 std::optional<T> get(std::string_view key) const;
245
246 //! Returns true if @p key is present in this block's option list, even if unset.
247 bool has_option(std::string_view key) const {
248 const auto option = std::find(m_options.crbegin(), m_options.crend(), key);
249 return !(option == m_options.crend());
250 }
251
252 //! Returns true if @p key is present and has a non-default, non-empty value.
253 bool option_is_set(std::string_view key) const {
254 return !(get(key) == std::nullopt);
255 }
256
257 //! Returns value of @p key in a sequence of nested blocks, or @p default_value.
258 template <typename T>
259 T get(std::initializer_list<std::string> blocks, std::string_view key,
260 T default_value) const;
261
262 //! Returns optional value of @p key in a sequence of nested blocks; empty if not found.
263 template <typename T>
264 std::optional<T> get(std::initializer_list<std::string> blocks,
265 std::string_view key) const;
266
267 //! Returns an optional copy of the child block named @p name; empty if not found.
268 inline std::optional<InputBlock> getBlock(std::string_view name) const;
269
270 //! Returns an optional copy of a block found by traversing @p blocks then looking for @p name.
271 inline std::optional<InputBlock>
272 getBlock(std::initializer_list<std::string> blocks,
273 std::string_view name) const;
274
275 //! Returns a copy of the child block named @p name, or an empty block if not found.
276 inline InputBlock get_block(std::string_view name) const {
277 auto temp = getBlock(name);
278 if (temp)
279 return *temp;
280 return InputBlock{};
281 }
282
283 //! Returns true if a child block named @p name exists in this block.
284 bool has_block(std::string_view name) const {
285 auto temp = getBlock(name);
286 return temp != std::nullopt;
287 }
288
289 //! Returns true if a block named @p name exists within the given nested @p blocks.
290 bool has_block(std::initializer_list<std::string> blocks,
291 std::string_view name) const {
292 auto temp = getBlock(blocks, name);
293 return temp != std::nullopt;
294 }
295
296 //! Returns the raw Option struct for @p key; rarely needed directly.
297 inline std::optional<Option> getOption(std::string_view key) const;
298
299 //! Prints the block contents to @p os in Block{option=value;} format.
300 inline void print(std::ostream &os = std::cout, int indent_depth = 0) const;
301
302 /*!
303 @brief Validates options and sub-blocks against an allowed list.
304 @details
305 Checks each option and sub-block in the nested path @p blocks against
306 @p list. If any are not found, a warning is printed along with the nearest
307 spelling suggestion, and false is returned. If @p print is true, the full
308 list of allowed options and descriptions is always printed.
309
310 The list entries are pairs of {option_name, description}. Blocks are
311 identified by a trailing `{}` in the name, e.g., "SubBlock{}".
312 Descriptions support self-documenting input files.
313 */
314 inline bool
315 check(std::initializer_list<std::string> blocks,
316 const std::vector<std::pair<std::string, std::string>> &list,
317 bool print = false) const;
318
319 //! Validates options in the current block against @p list. See check() overload for details.
320 inline bool
321 check(const std::vector<std::pair<std::string, std::string>> &list,
322 bool print = false) const {
323 return checkBlock(list, print);
324 }
325
326private:
327 inline bool
328 checkBlock(const std::vector<std::pair<std::string, std::string>> &list,
329 bool print = false) const;
330
331 //! Return a pointer to a block. Allows editing of blocks
332 inline InputBlock *getBlock_ptr(std::string_view name);
333 inline const InputBlock *getBlock_cptr(std::string_view name) const;
334
335 // Allows returning std::vector: comma-separated list input
336 template <typename T>
337 std::optional<std::vector<T>> get_vector(std::string_view key) const;
338
339 // Allows returning std::array: comma-separated list input
340 template <typename T, std::size_t N>
341 std::optional<std::array<T, N>> get_array(std::string_view key) const;
342
343 inline void add_option(std::string_view in_string);
344 inline void add_blocks_from_string(std::string_view string, bool merge);
345 inline void consolidate();
346};
347
348//==============================================================================
349//==============================================================================
350void InputBlock::add(InputBlock block, bool merge) {
351 auto existing_block = getBlock_ptr(block.m_name);
352 if (merge && existing_block) {
353 existing_block->m_options.insert(existing_block->m_options.end(),
354 block.m_options.cbegin(),
355 block.m_options.cend());
356 } else {
357 m_blocks.push_back(block);
358 }
359}
360
361//==============================================================================
362void InputBlock::add(Option option) { m_options.push_back(option); }
363void InputBlock::add(const std::vector<Option> &options) {
364 for (const auto &option : options)
365 m_options.push_back(option);
366}
367//==============================================================================
368void InputBlock::add(const std::string &string, bool merge) {
369
370 add_blocks_from_string(
372 merge);
373}
374
375//==============================================================================
376bool operator==(InputBlock block, std::string_view name) {
377 return qip::ci_wc_compare(name, block.m_name);
378}
379bool operator==(std::string_view name, InputBlock block) {
380 return block == name;
381}
382bool operator!=(InputBlock block, std::string_view name) {
383 return !(block == name);
384}
385bool operator!=(std::string_view name, InputBlock block) {
386 return !(block == name);
387}
388
389//==============================================================================
390template <typename T>
391std::optional<T> InputBlock::get(std::string_view key) const {
392 if constexpr (IsVector<T>::v) {
393 return get_vector<typename IsVector<T>::t>(key);
394 } else if constexpr (IsArray<T>::v) {
395 return get_array<typename IsArray<T>::t, IsArray<T>::size>(key);
396 } else if constexpr (std::is_same_v<T, bool>) {
397 const auto option = std::find(m_options.crbegin(), m_options.crend(), key);
398 if (option == m_options.crend())
399 return std::nullopt;
400 if (qip::ci_wc_compare("default", option->value_str) ||
401 option->value_str == "")
402 return std::nullopt;
403 const auto &str = option->value_str;
404 if (qip::ci_wc_compare("true", str) || qip::ci_wc_compare("yes", str) ||
405 qip::ci_wc_compare("y", str))
406 return true;
407 return false;
408 } else {
409 // Use reverse iterators so that we find _last_ option that matches key
410 // i.e., assume later options override earlier ones.
411 const auto option = std::find(m_options.crbegin(), m_options.crend(), key);
412 if (option == m_options.crend())
413 return std::nullopt;
414 if (qip::ci_wc_compare("default", option->value_str) ||
415 option->value_str == "")
416 return std::nullopt;
417 return parse_str_to_T<T>(option->value_str);
418 }
419}
420
421// special function; allows return of std::vector (for comma-separated list
422// input). Optional of vector is kind of redundant, but is this way so it
423// aligns with the other functions (checks if optional is empty when deciding
424// if should return the default value)
425template <typename T>
426std::optional<std::vector<T>>
427InputBlock::get_vector(std::string_view key) const {
428 // Use reverse iterators so that we find _last_ option that matches key
429 // i.e., assume later options override earlier ones.
430 std::vector<T> out;
431 const auto option = std::find(m_options.crbegin(), m_options.crend(), key);
432 if (option == m_options.crend())
433 return std::nullopt;
434 if (option->value_str == "")
435 return std::nullopt;
436 std::stringstream ss(option->value_str);
437 while (ss.good()) {
438 // note: *very* innefficient
439 std::string substr;
440 std::getline(ss, substr, ',');
441 out.push_back(parse_str_to_T<T>(substr));
442 }
443 return out;
444}
445
446template <typename T, std::size_t N>
447std::optional<std::array<T, N>>
448InputBlock::get_array(std::string_view key) const {
449 // Use reverse iterators so that we find _last_ option that matches key
450 // i.e., assume later options override earlier ones.
451 std::array<T, N> out;
452 const auto option = std::find(m_options.crbegin(), m_options.crend(), key);
453 if (option == m_options.crend())
454 return std::nullopt;
455 if (option->value_str == "")
456 return std::nullopt;
457 std::stringstream ss(option->value_str);
458 std::size_t index = 0;
459 while (ss.good()) {
460 // note: *very* innefficient
461 std::string substr;
462 std::getline(ss, substr, ',');
463 // out.push_back(parse_str_to_T<T>(substr));
464 out.at(index) = parse_str_to_T<T>(substr);
465 ++index;
466 }
467 return out;
468}
469
470template <typename T>
471T InputBlock::get(std::string_view key, T default_value) const {
472 static_assert(!std::is_same_v<T, const char *>,
473 "Cannot use get with const char* - use std::string");
474 return get<T>(key).value_or(default_value);
475}
476
477template <typename T>
478T InputBlock::get(std::initializer_list<std::string> blocks,
479 std::string_view key, T default_value) const {
480 return get<T>(blocks, key).value_or(default_value);
481}
482
483template <typename T>
484std::optional<T> InputBlock::get(std::initializer_list<std::string> blocks,
485 std::string_view key) const {
486 // Find key in nested blocks
487 const InputBlock *pB = this;
488 for (const auto &block : blocks) {
489 pB = pB->getBlock_cptr(block);
490 if (pB == nullptr)
491 return std::nullopt;
492 }
493 return pB->get<T>(key);
494}
495
496//==============================================================================
497std::optional<InputBlock> InputBlock::getBlock(std::string_view name) const {
498 // note: by copy!
499 const auto block = std::find(m_blocks.crbegin(), m_blocks.crend(), name);
500 if (block == m_blocks.crend())
501 return {};
502 return *block;
503}
504
505std::optional<InputBlock>
506InputBlock::getBlock(std::initializer_list<std::string> blocks,
507 std::string_view name) const {
508 // note: by copy!
509 const InputBlock *pB = this;
510 for (const auto &block : blocks) {
511 pB = pB->getBlock_cptr(block);
512 if (pB == nullptr)
513 return std::nullopt;
514 }
515 return pB->getBlock(name);
516}
517
518//==============================================================================
519std::optional<Option> InputBlock::getOption(std::string_view key) const {
520 // Use reverse iterators so that we find _last_ option that matches key
521 // i.e., assume later options override earlier ones.
522 const auto option = std::find(m_options.crbegin(), m_options.crend(), key);
523 if (option != m_options.crend())
524 return *option;
525 return {};
526}
527
528//==============================================================================
529void InputBlock::print(std::ostream &os, int depth) const {
530
531 std::string indent = "";
532 for (int i = 1; i < depth; ++i)
533 indent += " ";
534
535 // Don't print outer-most name
536 if (depth != 0)
537 os << indent << m_name << " { ";
538
539 const auto multi_entry = (!m_blocks.empty() || (m_options.size() > 1));
540
541 if (depth != 0 && multi_entry)
542 os << "\n";
543
544 for (const auto &[key, value] : m_options) {
545 os << (depth != 0 && multi_entry ? indent + " " : "");
546 if (value == "")
547 os << key << ';';
548 else
549 os << key << " = " << value << ';';
550 os << (multi_entry ? '\n' : ' ');
551 }
552
553 for (const auto &block : m_blocks)
554 block.print(os, depth + 1);
555
556 if (depth != 0 && multi_entry)
557 os << indent;
558
559 if (depth != 0)
560 os << "}\n";
561}
562
563//==============================================================================
564bool InputBlock::checkBlock(
565 const std::vector<std::pair<std::string, std::string>> &list,
566 bool print) const {
567 // Check each option NOT each sub block!
568 // For each input option stored, see if it is allowed
569 // "allowed" means appears in list
570 bool all_ok = true;
571 for (const auto &option : m_options) {
572 const auto is_optionQ = [&](const auto &l) {
573 // return option.key == l.first;
574 return qip::ci_wc_compare(l.first, option.key);
575 };
576 const auto bad_option =
577 !std::any_of(list.cbegin(), list.cend(), is_optionQ);
578 const auto help = qip::ci_wc_compare("help", option.key) ? true : false;
579 if (help)
580 print = true;
581 if (bad_option && !help) {
582 all_ok = false;
583 fmt2::styled_print(fg(fmt::color::orange), "\nWARNING\n");
584 std::cout << "Unclear input option in " << m_name << ": " << option.key
585 << " = " << option.value_str << ";\n"
586 << "Option may be ignored!\n"
587 << "Check spelling (or update list of options)\n";
588 // spell-check + nearest suggestion:
589 auto compare_sc = [&option](const auto &s1, const auto &s2) {
590 return qip::ci_Levenstein(s1.first, option.key) <
591 qip::ci_Levenstein(s2.first, option.key);
592 };
593 auto guess = std::min_element(list.cbegin(), list.cend(), compare_sc);
594 if (guess != list.cend()) {
595 std::cout << "\nDid you mean: " << guess->first << " ?\n";
596 }
597 }
598 }
599
600 using namespace std::string_literals;
601 for (const auto &block : m_blocks) {
602 const auto is_blockQ = [&](const auto &b) {
603 return qip::ci_wc_compare(std::string{block.name()} + "{}"s, b.first);
604 };
605 const auto bad_block = !std::any_of(list.cbegin(), list.cend(), is_blockQ);
606 if (bad_block) {
607 all_ok = false;
608 fmt2::styled_print(fg(fmt::color::orange), "\nWARNING\n");
609 std::cout << "Unclear input block within " << m_name << ": "
610 << block.name() << "{}\n"
611 << "Block and containing options may be ignored!\n"
612 << "Check spelling (or update list of options)\n";
613 // spell-check + nearest suggestion:
614 auto compare_sc = [&block](const auto &s1, const auto &s2) {
615 return qip::ci_Levenstein(s1.first, block.name()) <
616 qip::ci_Levenstein(s2.first, block.name());
617 };
618 auto guess = std::min_element(list.cbegin(), list.cend(), compare_sc);
619 if (guess != list.cend()) {
620 std::cout << "\nDid you mean: " << guess->first << " ?\n";
621 }
622 }
623 }
624
625 if (!all_ok || print) {
626 fmt2::styled_print(fg(fmt::color::light_blue),
627 "\n// Available {} options/blocks\n", m_name);
628 fmt2::styled_print(fmt::emphasis::bold, m_name);
629 std::cout << "{\n";
630 std::for_each(list.cbegin(), list.cend(), [](const auto &s) {
631 const auto option_is_block = s.first.back() == '}';
632 const auto leading_spaces = s.first.empty() ? ""s : " ";
633 if (!s.first.empty()) {
634 std::cout << " " << s.first << (option_is_block ? "\n" : ";\n");
635 }
636 if (!s.second.empty()) {
637 fmt2::styled_print(fg(fmt::color::light_blue), "{}\n",
638 qip::wrap(s.second, 60, leading_spaces + " // "));
639 } else {
640 std::cout << "\n";
641 }
642 });
643 std::cout << "}\n\n";
644 }
645 return all_ok;
646}
647
648//! Check one of the sub-blocks
649bool InputBlock::check(
650 std::initializer_list<std::string> blocks,
651 const std::vector<std::pair<std::string, std::string>> &list,
652 bool print) const {
653 // Find key in nested blocks
654 const InputBlock *pB = this;
655 for (const auto &block : blocks) {
656 pB = pB->getBlock_cptr(block);
657 if (pB == nullptr) {
658 // Did not fund nested block... may be fine
659 // Return true, since a missing block is not an issue (or sepparate
660 // issue) We are checking to see if blocks exist that shouldn't
661 return true;
662 }
663 }
664 return pB->check(list, print);
665}
666
667//==============================================================================
668void InputBlock::add_blocks_from_string(std::string_view string, bool merge) {
669
670 // Expects that string has comments and spaces removed already
671
672 auto start = 0ul;
673 while (start < string.length()) {
674
675 // Find the first of either next ';' or open '{'
676 // This is the end of the next input option, or start of block
677 auto end = std::min(string.find(';', start), string.find('{', start));
678 if (end > string.length() || start >= end)
679 break;
680
681 if (string.at(end) == ';') {
682 // end of option:
683
684 this->add_option(string.substr(start, end - start));
685
686 } else {
687 // start of block
688
689 // 'name' directly preceeds "{"
690 const auto block_name = string.substr(start, end - start);
691 start = end + 1;
692
693 // Now, find *matching* close '}' - ensure balanced
694 int depth_count = 1;
695 auto next_start = start; // + 1;
696 while (depth_count != 0) {
697 if (next_start > string.length())
698 break;
699 const auto next_end =
700 std::min(string.find('{', next_start), string.find('}', next_start));
701 if (next_end > string.length())
702 break;
703
704 // count depth of bracket nesting:
705 if (string.at(next_end) == '{')
706 ++depth_count;
707 else
708 --depth_count;
709
710 if (depth_count == 0) {
711 end = next_end;
712 break;
713 }
714 if (depth_count > 100) {
715 std::cerr << "FAIL 271 in InputBlock::add_blocks_from_string: Depth "
716 "error. Check balanced {} in input\n";
717 end = next_end;
718 break;
719 }
720 next_start = next_end + 1;
721 }
722
723 // Add a new block, populate it with string. Recursive, since blocks may
724 // contain blocks
725 auto &block = m_blocks.emplace_back(block_name);
726
727 if (end > start)
728 block.add_blocks_from_string(string.substr(start, end - start), merge);
729 }
730
731 start = end + 1;
732 }
733
734 // Merge duplicated blocks.
735 if (merge)
736 consolidate();
737 // No - want ability to have multiple blocks of same name
738}
739
740//==============================================================================
741void InputBlock::add_option(std::string_view in_string) {
742 const auto pos = in_string.find('=');
743 const auto option = in_string.substr(0, pos);
744 const auto value = pos < in_string.length() ? in_string.substr(pos + 1) : "";
745 m_options.push_back({std::string(option), std::string(value)});
746}
747
748//==============================================================================
749InputBlock *InputBlock::getBlock_ptr(std::string_view name) {
750 auto block = std::find(m_blocks.rbegin(), m_blocks.rend(), name);
751 if (block == m_blocks.rend())
752 return nullptr;
753 return &(*block);
754}
755
756const InputBlock *InputBlock::getBlock_cptr(std::string_view name) const {
757 auto block = std::find(m_blocks.crbegin(), m_blocks.crend(), name);
758 if (block == m_blocks.rend())
759 return nullptr;
760 return &(*block);
761}
762
763//==============================================================================
764void InputBlock::consolidate() {
765 for (auto bl = m_blocks.end() - 1; bl != m_blocks.begin() - 1; --bl) {
766 bl->consolidate();
767 auto bl2 = std::find(m_blocks.begin(), bl, bl->name());
768 if (bl2 != bl) {
769 bl2->m_options.insert(bl2->m_options.end(), bl->m_options.cbegin(),
770 bl->m_options.cend());
771 m_blocks.erase(bl);
772 }
773 }
774}
775
776//==============================================================================
777//==============================================================================
778//==============================================================================
779inline std::string expandIncludes(std::string str) {
780 const std::string include_text = "#include";
781
782 for (auto ipos = str.find(include_text); ipos != std::string::npos;
783 ipos = str.find(include_text)) {
784 const auto start = std::min(str.find('"', ipos), str.find('<', ipos));
785 const auto end =
786 std::min(str.find('"', start + 1), str.find('>', start + 1));
787 const auto fname = str.substr(start + 1, end - start - 1);
788 str.erase(ipos, end - ipos + 1);
789 std::ifstream ifile(fname);
790 if (ifile.good()) {
791 str.insert(ipos, removeComments(file_to_string(ifile)));
792 }
793 }
794
795 return str;
796}
797
798//==============================================================================
799inline std::string removeSpaces(std::string str) {
800
801 bool inside = false;
802 auto lambda = [&inside](unsigned char x) {
803 if (x == '\"' || x == '\'')
804 inside = !inside;
805 return ((x == ' ' || x == '\t' || x == '\n') && !inside);
806 };
807
808 str.erase(std::remove_if(str.begin(), str.end(), lambda), str.end());
809
810 return str;
811}
812
813inline std::string removeQuoteMarks(std::string str) {
814
815 // remove ' and "
816 str.erase(std::remove_if(str.begin(), str.end(),
817 [](unsigned char x) { return x == '\''; }),
818 str.end());
819 str.erase(std::remove_if(str.begin(), str.end(),
820 [](unsigned char x) { return x == '\"'; }),
821 str.end());
822
823 return str;
824}
825
826//==============================================================================
827inline void removeBlockComments(std::string &input) {
828 for (auto posi = input.find("/*"); posi != std::string::npos;
829 posi = input.find("/*")) {
830 auto posf = input.find("*/");
831 if (posf != std::string::npos) {
832 input = input.substr(0, posi) + input.substr(posf + 2);
833 } else {
834 input = input.substr(0, posi);
835 }
836 }
837}
838
839//==============================================================================
840inline std::string removeComments(const std::string &input) {
841 std::string str = "";
842 {
843 std::string line;
844 std::stringstream stream1(input);
845 while (std::getline(stream1, line, '\n')) {
846 // const auto comm1 = line.find('!'); // nb: char, NOT string literal!
847 // auto comm2 = line.find('#');
848 const auto comm3 = line.find("//"); // str literal here
849 // const auto comm = std::min({comm1, comm3});
850 str += line.substr(0, comm3);
851 str += '\n';
852 }
853 }
854 removeBlockComments(str);
855
856 return str;
857}
858
859//==============================================================================
860template <typename T>
861T inline parse_str_to_T(const std::string &value_as_str) {
862 if constexpr (std::is_same_v<T, std::string>) {
863 // already a string, just return value
864 return value_as_str;
865 } else {
866 // T is not a string: convert using stringstream
867 T value_T;
868 std::stringstream ss(value_as_str);
869 ss >> value_T;
870 return value_T;
871 }
872}
873
874//==============================================================================
875inline std::string file_to_string(const std::istream &file) {
876 std::string out;
877 if (!file)
878 return "";
879 // Horribly inneficient...
880 std::ostringstream ss;
881 ss << file.rdbuf();
882 return ss.str();
883}
884
885} // namespace IO
Holds a named list of key=value options and nested InputBlocks.
Definition InputBlock.hpp:154
InputBlock(std::string_view name, const std::istream &file)
Constructs by reading and parsing a plain-text file stream.
Definition InputBlock.hpp:175
bool check(const std::vector< std::pair< std::string, std::string > > &list, bool print=false) const
Validates options in the current block against list. See check() overload for details.
Definition InputBlock.hpp:321
bool has_block(std::string_view name) const
Returns true if a child block named name exists in this block.
Definition InputBlock.hpp:284
std::optional< Option > getOption(std::string_view key) const
Returns the raw Option struct for key; rarely needed directly.
Definition InputBlock.hpp:519
InputBlock(std::string_view name, const std::string &string_input)
Constructs by parsing string_input in Block{option=value;} format.
Definition InputBlock.hpp:169
void add(InputBlock block, bool merge=false)
Appends or merges a child InputBlock.
Definition InputBlock.hpp:350
bool check(std::initializer_list< std::string > blocks, const std::vector< std::pair< std::string, std::string > > &list, bool print=false) const
Validates options and sub-blocks against an allowed list.
Definition InputBlock.hpp:649
void print(std::ostream &os=std::cout, int indent_depth=0) const
Prints the block contents to os in Block{option=value;} format.
Definition InputBlock.hpp:529
const std::vector< Option > & options() const
Returns const reference to the list of options.
Definition InputBlock.hpp:213
InputBlock(std::string_view name, std::initializer_list< Option > options={})
Constructs from a literal list of Option structs.
Definition InputBlock.hpp:165
void merge(const std::string &string)
Parses string and merges any duplicate block names.
Definition InputBlock.hpp:207
std::string_view name() const
Returns the name of this block.
Definition InputBlock.hpp:210
InputBlock get_block(std::string_view name) const
Returns a copy of the child block named name, or an empty block if not found.
Definition InputBlock.hpp:276
const std::vector< InputBlock > & blocks() const
Returns const reference to the list of child blocks.
Definition InputBlock.hpp:216
std::optional< InputBlock > getBlock(std::string_view name) const
Returns an optional copy of the child block named name; empty if not found.
Definition InputBlock.hpp:497
bool option_is_set(std::string_view key) const
Returns true if key is present and has a non-default, non-empty value.
Definition InputBlock.hpp:253
bool has_option(std::string_view key) const
Returns true if key is present in this block's option list, even if unset.
Definition InputBlock.hpp:247
T get(std::string_view key, T default_value) const
Returns the value of key, or default_value if not found.
Definition InputBlock.hpp:471
void merge(InputBlock block)
Merges block into an existing block of the same name, or appends it.
Definition InputBlock.hpp:189
friend bool operator==(InputBlock block, std::string_view name)
Equality/inequality compare by block name (case-insensitive).
Definition InputBlock.hpp:376
bool has_block(std::initializer_list< std::string > blocks, std::string_view name) const
Returns true if a block named name exists within the given nested blocks.
Definition InputBlock.hpp:290
InputBlock()
Default constructor; name will be blank.
Definition InputBlock.hpp:162
In-out (timers, profilers, and read/write data)
Definition ChronoTimer.hpp:9
std::string time()
Returns current local time as a string, e.g. "13:01:02".
Definition InputBlock.hpp:103
std::string date()
Returns current local date as a string, e.g. "2026-05-14".
Definition InputBlock.hpp:95
void unkown_option(std::string_view test_string, const std::vector< std::string > &list)
Prints unkown option warning, with suggested alternative.
Definition InputBlock.hpp:19
void removeBlockComments(std::string &input)
Removes all c++ style block comments from a string.
Definition InputBlock.hpp:827
std::string expandIncludes(std::string input)
Expands "#include" files.
Definition InputBlock.hpp:779
std::string time_date()
Returns current local date and time as a string, e.g. "2026-05-14 13:01:02".
Definition InputBlock.hpp:87
std::string removeSpaces(std::string str)
Removes all white space (space, tab, newline), except for those in quotes.
Definition InputBlock.hpp:799
void print_line(const char c=' *', const int num=80)
Prints a line of 'c' characters (dflt '*'), num chars long (dflt 80) to cout.
Definition InputBlock.hpp:79
std::string file_to_string(const std::istream &file)
Parses entire file into string. Note: v. inefficient.
Definition InputBlock.hpp:875
std::string removeComments(const std::string &input)
Removes all c++ style comments from a string (block and line)
Definition InputBlock.hpp:840
T parse_str_to_T(const std::string &value_as_str)
Parses a string to type T by stringstream.
Definition InputBlock.hpp:861
std::string removeQuoteMarks(std::string str)
Removes all quote marks.
Definition InputBlock.hpp:813
std::string ci_closest_match(const std::string_view test_string, const std::vector< std::string > &list)
Returns the closest match (case insensitive) to test_string in list, using ci_Levenstein distance.
Definition String.hpp:246
auto ci_Levenstein(std::string_view a, std::string_view b)
Case-insensitive version of Levenstein.
Definition String.hpp:208
bool ci_wc_compare(std::string_view s1, std::string_view s2)
Case-insensitive version of wildcard_compare.
Definition String.hpp:155
Compile-time trait: IsArray<T>::v is true if T is std::array. IsArray<T>::t is the element type; IsAr...
Definition InputBlock.hpp:65
Compile-time trait: IsVector<T>::v is true if T is std::vector. IsVector<T>::t is the element type.
Definition InputBlock.hpp:53
Simple struct; holds key-value pair, both strings. == compares key.
Definition InputBlock.hpp:113