Step 7 of 7
Challenge: load a config
Now put the pieces together in the kind of code every service has: a config loader. It must report precise errors, because "invalid config" with no line number wastes everyone's time. Good error messages say where and what.
Here's a smaller loader for a shopping list with item: qty lines, using the same techniques you'll need:
#include <iostream>
#include <map>
#include <sstream>
#include <string>
struct ListResult {
std::map<std::string, int> items;
std::string error; // empty means success
};
ListResult load_list(const std::string& text) {
ListResult r;
std::istringstream in(text);
std::string line;
int n = 0;
while (std::getline(in, line)) {
n++; // count every line, even skipped ones
if (line.empty()) continue;
auto colon = line.find(':');
if (colon == std::string::npos) {
r.error = "line " + std::to_string(n) + ": missing :";
return r;
}
std::string item = line.substr(0, colon);
if (r.items.contains(item)) {
r.error = "line " + std::to_string(n) + ": " + item + " listed twice";
return r;
}
r.items[item] = std::stoi(line.substr(colon + 1));
}
return r;
}
int main() {
ListResult ok = load_list("eggs: 12\n\nmilk: 2\n");
std::cout << ok.items.size() << " items, eggs " << ok.items["eggs"] << "\n";
std::cout << load_list("eggs: 12\nmilk 2\n").error << "\n";
std::cout << load_list("tea: 1\n\ntea: 3\n").error << "\n";
}
2 items, eggs 12
line 2: missing :
line 3: tea listed twice
Details that matter
- The line counter increases for every line, including skipped blank ones. Otherwise the reported number wouldn't match what the user sees in their editor. The third example reports line 3, not 2.
- The function stops at the first error and returns. Carrying on after a broken line tends to produce confusing follow-up errors.
- The error text is built with
std::to_string(n)and string concatenation.
Your task's extra rules
Your loader also skips lines starting with # (check line[0] == '#' after the empty check, so you never index an empty string), and rejects an empty key, which is what you get for a line like =value (the = is at index 0). Check the rules in the order listed.
Your task
Write ConfigResult load_config(const std::string& text). The text has one key=value per line:
- blank lines and lines starting with
#are skipped - a line with no
=fails with"line N: missing =" - an empty key fails with
"line N: empty key" - a key seen twice fails with
"line N: duplicate key KEY" - otherwise every pair goes into
values
Lines are numbered from 1. Stop at the first error.
Challenge 2
Write ArgsResult parse_args(const std::vector<std::string>& args) for command-line options. --name=value stores value, and a bare flag like --verbose stores "true". Arguments are numbered from 1:
- an argument not starting with
--fails with"argument N: expected --" - an empty name (
--or--=x) fails with"argument N: empty name" - a name given twice fails with
"argument N: duplicate NAME"
Stop at the first error.
Challenge 3
Turn raw config values into typed Settings. Write SettingsResult to_settings(const std::map<std::string, std::string>& v):
portis required: missing gives"missing port", anything but digits gives"port is not a number", and outside 1 to 65535 gives"port out of range"hostdefaults to"localhost"debugdefaults to false and must be"true"or"false", otherwise"debug must be true or false"
Check port first, then debug.