cppfig 0.1.0
Modern C++20 compile-time type-safe configuration library
Loading...
Searching...
No Matches
conf.h
Go to the documentation of this file.
1#pragma once
2
3#include <istream>
4#include <sstream>
5#include <string>
6#include <string_view>
7#include <utility>
8#include <vector>
9
10#include "cppfig/status.h"
11#include "cppfig/value.h"
12
13namespace cppfig {
14
38
40 static auto Parse(std::istream& is) -> StatusOr<Value>
41 {
42 Value result = Value::Object();
43 std::string line;
44 int line_number = 0;
45
46 while (std::getline(is, line)) {
47 ++line_number;
48
49 auto trimmed = Trim(line);
50
51 // Skip empty lines and comments
52 if (trimmed.empty() || trimmed[0] == '#') {
53 continue;
54 }
55
56 // key = value
57 auto eq_pos = trimmed.find('=');
58 if (eq_pos == std::string::npos) {
59 return InvalidArgumentError("conf parse error: missing '=' on line " + std::to_string(line_number));
60 }
61
62 std::string key = Trim(trimmed.substr(0, eq_pos));
63 std::string value_str = Trim(trimmed.substr(eq_pos + 1));
64
65 result.SetAtPath(key, InferValue(value_str));
66 }
67
68 return result;
69 }
70
72 static auto ParseString(std::string_view str) -> StatusOr<Value>
73 {
74 std::istringstream stream { std::string(str) };
75 return Parse(stream);
76 }
77
79 static auto Stringify(const Value& data) -> std::string
80 {
81 std::vector<std::pair<std::string, const Value*>> leaves;
82 CollectLeaves(data, "", leaves);
83
84 std::ostringstream stream;
85 for (const auto& [path, val] : leaves) {
86 stream << path << " = " << ValueToString(*val) << '\n';
87 }
88
89 return stream.str();
90 }
91
92private:
94 static auto Trim(std::string_view sv) -> std::string
95 {
96 auto start = sv.find_first_not_of(" \t\r\n");
97 if (start == std::string_view::npos) {
98 return "";
99 }
100 auto end = sv.find_last_not_of(" \t\r\n");
101 return std::string(sv.substr(start, end - start + 1));
102 }
103
105 static auto InferValue(const std::string& str) -> Value
106 {
107 // Quoted string
108 if (str.size() >= 2 && str.front() == '"' && str.back() == '"') {
109 return { str.substr(1, str.size() - 2) };
110 }
111
112 // Empty → empty string
113 if (str.empty()) {
114 return { std::string("") };
115 }
116
117 // Boolean
118 if (str == "true" || str == "yes" || str == "on") {
119 return { true };
120 }
121 if (str == "false" || str == "no" || str == "off") {
122 return { false };
123 }
124
125 // Integer
126 try {
127 std::size_t pos = 0;
128 auto int_val = std::stoll(str, &pos);
129 if (pos == str.size()) {
130 return { static_cast<std::int64_t>(int_val) };
131 }
132 }
133 catch (...) {
134 }
135
136 // Double (only if it contains a decimal point or exponent)
137 if (str.find('.') != std::string::npos || str.find('e') != std::string::npos || str.find('E') != std::string::npos) {
138 try {
139 std::size_t pos = 0;
140 auto double_val = std::stod(str, &pos);
141 if (pos == str.size()) {
142 return { double_val };
143 }
144 }
145 catch (...) {
146 }
147 }
148
149 // Unquoted string
150 return { str };
151 }
152
154 static auto ValueToString(const Value& val) -> std::string
155 {
156 if (val.IsNull()) {
157 return "";
158 }
159 if (val.IsBoolean()) {
160 return val.Get<bool>() ? "true" : "false";
161 }
162 if (val.IsInteger()) {
163 return std::to_string(val.Get<std::int64_t>());
164 }
165 if (val.IsDouble()) {
166 std::ostringstream stream;
167 stream << val.Get<double>();
168 return stream.str();
169 }
170 if (val.IsString()) {
171 const auto& str = val.Get<std::string>();
172 // Quote if the string contains special characters or is empty
173 bool needs_quoting = str.empty() || str.front() == ' ' || str.back() == ' ' || str.front() == '"' || str.find_first_of("=#\n\r") != std::string::npos;
174 if (needs_quoting) {
175 return "\"" + str + "\"";
176 }
177 return str;
178 }
179 return "";
180 }
181
183 static void CollectLeaves(const Value& node, const std::string& prefix, std::vector<std::pair<std::string, const Value*>>& leaves)
184 {
185 if (node.IsObject()) {
186 for (const auto& [key, val] : node.Items()) {
187 std::string path = prefix.empty() ? key : prefix + "." + key;
188 CollectLeaves(val, path, leaves);
189 }
190 }
191 else {
192 leaves.emplace_back(prefix, &node);
193 }
194 }
195};
196
197} // namespace cppfig
A value-or-error type, similar to std::expected (C++23).
Definition status.h:100
A self-contained, recursive value type for configuration data.
Definition value.h:26
void SetAtPath(std::string_view path, const Value &value)
Sets a value at a dot-separated path, creating intermediate objects.
Definition value.h:285
static auto Object() -> Value
Creates an empty object value.
Definition value.h:133
C++20 compile-time type-safe configuration library.
Definition conf.h:13
auto InvalidArgumentError(std::string message) -> Status
Returns an InvalidArgument error status.
Definition status.h:61
Flat key-value .conf serializer — the default, zero-dependency serializer.
Definition conf.h:36
static auto Parse(std::istream &is) -> StatusOr< Value >
Parses a .conf stream into a Value tree.
Definition conf.h:40
static auto ParseString(std::string_view str) -> StatusOr< Value >
Parses a .conf string into a Value tree.
Definition conf.h:72
static auto Stringify(const Value &data) -> std::string
Converts a Value tree to flat key = value lines.
Definition conf.h:79