| 187 | } |
| 188 | |
| 189 | static std::map<std::string, std::map<std::string, std::string>> parse_ini_from_file(const std::string & path) { |
| 190 | std::map<std::string, std::map<std::string, std::string>> parsed; |
| 191 | |
| 192 | if (!std::filesystem::exists(path)) { |
| 193 | throw std::runtime_error("preset file does not exist: " + path); |
| 194 | } |
| 195 | |
| 196 | std::ifstream file(path); |
| 197 | if (!file.good()) { |
| 198 | throw std::runtime_error("failed to open server preset file: " + path); |
| 199 | } |
| 200 | |
| 201 | std::string contents((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); |
| 202 | |
| 203 | static const auto parser = build_peg_parser([](auto & p) { |
| 204 | // newline ::= "\r\n" / "\n" / "\r" |
| 205 | auto newline = p.rule("newline", p.literal("\r\n") | p.literal("\n") | p.literal("\r")); |
| 206 | |
| 207 | // ws ::= [ \t]* |
| 208 | auto ws = p.rule("ws", p.chars("[ \t]", 0, -1)); |
| 209 | |
| 210 | // comment ::= [;#] (!newline .)* |
| 211 | auto comment = p.rule("comment", p.chars("[;#]", 1, 1) + p.zero_or_more(p.negate(newline) + p.any())); |
| 212 | |
| 213 | // eol ::= ws comment? (newline / EOF) |
| 214 | auto eol = p.rule("eol", ws + p.optional(comment) + (newline | p.end())); |
| 215 | |
| 216 | // ident ::= [a-zA-Z_] [a-zA-Z0-9_.-]* |
| 217 | auto ident = p.rule("ident", p.chars("[a-zA-Z_]", 1, 1) + p.chars("[a-zA-Z0-9_.-]", 0, -1)); |
| 218 | |
| 219 | // value ::= (!eol-start .)* |
| 220 | auto eol_start = p.rule("eol-start", ws + (p.chars("[;#]", 1, 1) | newline | p.end())); |
| 221 | auto value = p.rule("value", p.zero_or_more(p.negate(eol_start) + p.any())); |
| 222 | |
| 223 | // header-line ::= "[" ws ident ws "]" eol |
| 224 | auto header_line = p.rule("header-line", "[" + ws + p.tag("section-name", p.chars("[^]]")) + ws + "]" + eol); |
| 225 | |
| 226 | // kv-line ::= ident ws "=" ws value eol |
| 227 | auto kv_line = p.rule("kv-line", p.tag("key", ident) + ws + "=" + ws + p.tag("value", value) + eol); |
| 228 | |
| 229 | // comment-line ::= ws comment (newline / EOF) |
| 230 | auto comment_line = p.rule("comment-line", ws + comment + (newline | p.end())); |
| 231 | |
| 232 | // blank-line ::= ws (newline / EOF) |
| 233 | auto blank_line = p.rule("blank-line", ws + (newline | p.end())); |
| 234 | |
| 235 | // line ::= header-line / kv-line / comment-line / blank-line |
| 236 | auto line = p.rule("line", header_line | kv_line | comment_line | blank_line); |
| 237 | |
| 238 | // ini ::= line* EOF |
| 239 | auto ini = p.rule("ini", p.zero_or_more(line) + p.end()); |
| 240 | |
| 241 | return ini; |
| 242 | }); |
| 243 | |
| 244 | common_peg_parse_context ctx(contents); |
| 245 | const auto result = parser.parse(ctx); |
| 246 | if (!result.success()) { |
no test coverage detected