CppNCorr
C++ ncorr Digital Image Correlation engine
Loading...
Searching...
No Matches
ini.h
Go to the documentation of this file.
1#pragma once
23#include <cctype>
24#include <fstream>
25#include <map>
26#include <stdexcept>
27#include <string>
28
29namespace ncorr {
30
34class IniFile {
35 public:
37 IniFile() = default;
38
45 bool load(const std::string& path) {
46 std::ifstream file(path);
47 if (!file.is_open()) {
48 return false; // missing file => caller falls back to defaults
49 }
50 values_.clear();
51 std::string section;
52 std::string line;
53 int line_no = 0;
54 while (std::getline(file, line)) {
55 ++line_no;
56 // Strip a trailing '\r' so CRLF files parse correctly.
57 if (!line.empty() && line.back() == '\r') line.pop_back();
58
59 std::string content = strip_inline_comment(line);
60 content = trim(content);
61 if (content.empty()) continue;
62
63 // Section header: [name]
64 if (content.front() == '[') {
65 if (content.back() != ']') {
66 throw std::runtime_error("INI parse error at line " + std::to_string(line_no) +
67 ": malformed section header");
68 }
69 section = trim(content.substr(1, content.size() - 2));
70 continue;
71 }
72
73 // key = value
74 std::size_t eq = content.find('=');
75 if (eq == std::string::npos) {
76 throw std::runtime_error("INI parse error at line " + std::to_string(line_no) +
77 ": expected 'key = value'");
78 }
79 std::string key = trim(content.substr(0, eq));
80 std::string value = unquote(trim(content.substr(eq + 1)));
81 if (key.empty()) {
82 throw std::runtime_error("INI parse error at line " + std::to_string(line_no) +
83 ": empty key");
84 }
85 std::string full_key = section.empty() ? key : (section + "." + key);
86 values_[full_key] = value;
87 }
88 loaded_ = true;
89 return true;
90 }
91
93 bool has(const std::string& key) const { return values_.find(key) != values_.end(); }
94
96 std::string get(const std::string& key, const std::string& fallback = "") const {
97 auto it = values_.find(key);
98 return it == values_.end() ? fallback : it->second;
99 }
100
102 int get_int(const std::string& key, int fallback) const {
103 auto it = values_.find(key);
104 if (it == values_.end()) return fallback;
105 try {
106 return std::stoi(it->second);
107 } catch (const std::exception&) {
108 throw std::runtime_error("INI value for '" + key + "' is not a valid integer: '" +
109 it->second + "'");
110 }
111 }
112
114 double get_double(const std::string& key, double fallback) const {
115 auto it = values_.find(key);
116 if (it == values_.end()) return fallback;
117 try {
118 return std::stod(it->second);
119 } catch (const std::exception&) {
120 throw std::runtime_error("INI value for '" + key + "' is not a valid number: '" +
121 it->second + "'");
122 }
123 }
124
127 bool get_bool(const std::string& key, bool fallback) const {
128 auto it = values_.find(key);
129 if (it == values_.end()) return fallback;
130 std::string v = to_lower(it->second);
131 if (v == "true" || v == "1" || v == "yes" || v == "on") return true;
132 if (v == "false" || v == "0" || v == "no" || v == "off") return false;
133 throw std::runtime_error("INI value for '" + key + "' is not a valid boolean: '" +
134 it->second + "'");
135 }
136
138 const std::map<std::string, std::string>& values() const { return values_; }
139
140 private:
141 static std::string to_lower(std::string s) {
142 for (char& c : s) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
143 return s;
144 }
145
146 static std::string trim(const std::string& s) {
147 std::size_t b = s.find_first_not_of(" \t");
148 if (b == std::string::npos) return "";
149 std::size_t e = s.find_last_not_of(" \t");
150 return s.substr(b, e - b + 1);
151 }
152
153 // Remove an inline comment introduced by an unquoted '#' or ';'.
154 static std::string strip_inline_comment(const std::string& s) {
155 bool in_single = false, in_double = false;
156 for (std::size_t i = 0; i < s.size(); ++i) {
157 char c = s[i];
158 if (c == '\'' && !in_double)
159 in_single = !in_single;
160 else if (c == '"' && !in_single)
161 in_double = !in_double;
162 else if ((c == '#' || c == ';') && !in_single && !in_double) {
163 return s.substr(0, i);
164 }
165 }
166 return s;
167 }
168
169 // Strip a single matching pair of surrounding quotes, if present.
170 static std::string unquote(const std::string& s) {
171 if (s.size() >= 2 &&
172 ((s.front() == '"' && s.back() == '"') || (s.front() == '\'' && s.back() == '\''))) {
173 return s.substr(1, s.size() - 2);
174 }
175 return s;
176 }
177
178 std::map<std::string, std::string> values_;
179 bool loaded_ = false;
180};
181
182} // namespace ncorr
In-memory representation of a parsed INI file.
Definition ini.h:34
double get_double(const std::string &key, double fallback) const
Definition ini.h:114
IniFile()=default
Construct an empty INI store.
bool has(const std::string &key) const
Definition ini.h:93
bool get_bool(const std::string &key, bool fallback) const
Definition ini.h:127
int get_int(const std::string &key, int fallback) const
Definition ini.h:102
std::string get(const std::string &key, const std::string &fallback="") const
Definition ini.h:96
bool load(const std::string &path)
Parse an INI file from disk.
Definition ini.h:45
const std::map< std::string, std::string > & values() const
Definition ini.h:138