The Battle for Wesnoth  1.17.0-dev
save_index.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2021
3  by Jörg Hinrichs, David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "save_index.hpp"
17 
18 #include "config.hpp"
19 #include "filesystem.hpp"
20 #include "format_time_summary.hpp"
21 #include "game_end_exceptions.hpp"
22 #include "game_errors.hpp"
23 #include "gettext.hpp"
24 #include "log.hpp"
25 #include "preferences/game.hpp"
27 #include "serialization/parser.hpp"
28 #include "team.hpp"
29 
30 #include <boost/algorithm/string/replace.hpp>
31 #include <boost/iostreams/filter/gzip.hpp>
32 
33 static lg::log_domain log_engine("engine");
34 #define LOG_SAVE LOG_STREAM(info, log_engine)
35 #define ERR_SAVE LOG_STREAM(err, log_engine)
36 
37 static lg::log_domain log_enginerefac("enginerefac");
38 #define LOG_RG LOG_STREAM(info, log_enginerefac)
39 
40 namespace savegame
41 {
43 
44 void save_index_class::rebuild(const std::string& name)
45 {
46  std::time_t modified = filesystem::file_modified_time(dir_ + "/" + name);
47  rebuild(name, modified);
48 }
49 
50 void save_index_class::rebuild(const std::string& name, const std::time_t& modified)
51 {
52  log_scope("load_summary_from_file");
53 
54  config& summary = data(name);
55 
56  try {
57  config full;
58  std::string dummy;
59  read_save_file(dir_, name, full, &dummy);
60 
61  extract_summary_from_config(full, summary);
62  } catch(const game::load_game_failed&) {
63  summary["corrupt"] = true;
64  }
65 
66  summary["mod_time"] = std::to_string(static_cast<int>(modified));
68 }
69 
70 void save_index_class::remove(const std::string& name)
71 {
72  config& root = data();
73  root.remove_children("save", [&name](const config& d) { return name == d["save"]; });
75 }
76 
77 void save_index_class::set_modified(const std::string& name, const std::time_t& modified)
78 {
79  modified_[name] = modified;
80 }
81 
82 config& save_index_class::get(const std::string& name)
83 {
84  config& result = data(name);
85  std::time_t m = modified_[name];
86 
87  config::attribute_value& mod_time = result["mod_time"];
88  if(mod_time.empty() || mod_time.to_time_t() != m) {
89  rebuild(name, m);
90  }
91 
92  return result;
93 }
94 
95 const std::string& save_index_class::dir() const
96 {
97  return dir_;
98 }
99 
101 {
102  config &root = data();
103 
104  std::vector<std::string> filenames;
105  filesystem::get_files_in_dir(dir(), &filenames);
106 
107  if(root.all_children_count() > filenames.size()) {
108  root.remove_children("save", [&filenames](const config& d)
109  {
110  return std::find(filenames.begin(), filenames.end(), d["save"]) == filenames.end();
111  }
112  );
113  }
114 }
115 
117 {
118  log_scope("write_save_index()");
119 
120  if(read_only_) {
121  LOG_SAVE << "no-op: read_only instance";
122  return;
123  }
124 
125  if(clean_up_index_) {
126  clean_up_index();
127  clean_up_index_ = false;
128  }
129 
130  try {
132 
134  // TODO: maybe allow writing this using bz2 too?
135  write_gz(*stream, data());
136  } else {
137  write(*stream, data());
138  }
139  } catch(const filesystem::io_exception& e) {
140  ERR_SAVE << "error writing to save index file: '" << e.what() << "'" << std::endl;
141  }
142 }
143 
145  : loaded_(false)
146  , data_()
147  , modified_()
148  , dir_(dir)
149  , read_only_(true)
150  , clean_up_index_(true)
151 {
152 }
153 
156 {
157  read_only_ = false;
158 }
159 
160 config& save_index_class::data(const std::string& name)
161 {
162  config& cfg = data();
163  if(config& sv = cfg.find_child("save", "save", name)) {
165  return sv;
166  }
167 
168  config& res = cfg.add_child("save");
169  res["save"] = name;
170  return res;
171 }
172 
174 {
175  const std::string si_file = filesystem::get_save_index_file();
176 
177  // Don't try to load the file if it doesn't exist.
178  if(loaded_ == false && filesystem::file_exists(si_file)) {
179  try {
181  try {
182  read_gz(data_, *stream);
183  } catch(const boost::iostreams::gzip_error&) {
184  stream->seekg(0);
185  read(data_, *stream);
186  }
187  } catch(const filesystem::io_exception& e) {
188  ERR_SAVE << "error reading save index: '" << e.what() << "'" << std::endl;
189  } catch(const config::error& e) {
190  ERR_SAVE << "error parsing save index config file:\n" << e.message << std::endl;
191  data_.clear();
192  }
193 
194  loaded_ = true;
195  }
196 
197  return data_;
198 }
199 
201 {
202  for(config& leader : data.child_range("leader")) {
203  std::string leader_image = leader["leader_image"];
204  boost::algorithm::replace_all(leader_image, "\\", "/");
205 
206  leader["leader_image"] = leader_image;
207  }
208 }
209 
210 std::shared_ptr<save_index_class> save_index_class::default_saves_dir()
211 {
212  static auto instance = std::make_shared<save_index_class>(create_for_default_saves_dir::yes);
213  return instance;
214 }
215 
216 /** Get a list of available saves. */
217 std::vector<save_info> save_index_class::get_saves_list(const std::string* filter)
218 {
219  create_save_info creator(shared_from_this());
220 
221  std::vector<std::string> filenames;
222  filesystem::get_files_in_dir(dir(), &filenames);
223 
224  const auto should_remove = [filter](const std::string& filename) {
225  // Steam documentation indicates games can ignore their auto-generated 'steam_autocloud.vdf'.
226  // Reference: https://partner.steamgames.com/doc/features/cloud (under Steam Auto-Cloud section as of September 2021)
227  static const std::vector<std::string> to_ignore {"steam_autocloud.vdf"};
228 
229  if(std::find(to_ignore.begin(), to_ignore.end(), filename) != to_ignore.end()) {
230  return true;
231  } else if(filter) {
232  return filename.end() == std::search(filename.begin(), filename.end(), filter->begin(), filter->end());
233  }
234 
235  return false;
236  };
237 
238  filenames.erase(std::remove_if(filenames.begin(), filenames.end(), should_remove), filenames.end());
239 
240  std::vector<save_info> result;
241  std::transform(filenames.begin(), filenames.end(), std::back_inserter(result), creator);
242  std::sort(result.begin(), result.end(), save_info_less_time());
243 
244  return result;
245 }
246 
248 {
249  return save_index_->get(name());
250 }
251 
252 std::string save_info::format_time_local() const
253 {
254  if(std::tm* tm_l = std::localtime(&modified())) {
256  // TRANSLATORS: Day of week + month + day of month + year + 12-hour time, eg 'Tue Nov 02 2021, 1:59 PM'. Format for your locale.
257  ? _("%a %b %d %Y, %I:%M %p")
258  // TRANSLATORS: Day of week + month + day of month + year + 24-hour time, eg 'Tue Nov 02 2021, 13:59'. Format for your locale.
259  : _("%a %b %d %Y, %H:%M");
260 
261  return translation::strftime(format, tm_l);
262  }
263 
264  LOG_SAVE << "localtime() returned null for time " << this->modified() << ", save " << name();
265  return "";
266 }
267 
269 {
270  std::time_t t = modified();
271  return utils::format_time_summary(t);
272 }
273 
275 {
276  // This translatable string must be same one as in replay_savegame::create_initial_filename.
277  // TODO: we really shouldn't be relying on translatable strings like this, especially since
278  // old savefiles may have been created in a different language than the current UI language
279  const std::string replay_str = " " + _("replay");
280  if(a.modified() > b.modified()) {
281  return true;
282  } else if(a.modified() < b.modified()) {
283  return false;
284  } else if(a.name().find(replay_str) == std::string::npos && b.name().find(replay_str) != std::string::npos) {
285  // Special funky case; for files created in the same second,
286  // a replay file sorts less than a non-replay file. Prevents
287  // a timing-dependent bug where it may look like, at the end
288  // of a scenario, the replay and the autosave for the next
289  // scenario are displayed in the wrong order.
290  return true;
291  } else if(a.name().find(replay_str) != std::string::npos && b.name().find(replay_str) == std::string::npos) {
292  return false;
293  } else {
294  return a.name() > b.name();
295  }
296 }
297 
299  const std::string& name, const std::vector<std::string>& suffixes)
300 {
301  for(const std::string& suf : suffixes) {
302  filesystem::scoped_istream file_stream =
303  filesystem::istream_file(dir + "/" + name + suf);
304 
305  if(!file_stream->fail()) {
306  return file_stream;
307  }
308  }
309 
310  LOG_SAVE << "Could not open supplied filename '" << name << "'\n";
311  throw game::load_game_failed();
312 }
313 
314 void read_save_file(const std::string& dir, const std::string& name, config& cfg, std::string* error_log)
315 {
316  static const std::vector<std::string> suffixes{"", ".gz", ".bz2"};
317  filesystem::scoped_istream file_stream = find_save_file(dir, name, suffixes);
318 
319  cfg.clear();
320  try {
321  /*
322  * Test the modified name, since it might use a .gz
323  * file even when not requested.
324  */
325  if(filesystem::is_gzip_file(name)) {
326  read_gz(cfg, *file_stream);
327  } else if(filesystem::is_bzip2_file(name)) {
328  read_bz2(cfg, *file_stream);
329  } else {
330  read(cfg, *file_stream);
331  }
332  } catch(const std::ios_base::failure& e) {
333  LOG_SAVE << e.what();
334 
335  if(error_log) {
336  *error_log += e.what();
337  }
338  throw game::load_game_failed();
339  } catch(const config::error& err) {
340  LOG_SAVE << err.message;
341 
342  if(error_log) {
343  *error_log += err.message;
344  }
345 
346  throw game::load_game_failed();
347  }
348 
349  if(cfg.empty()) {
350  LOG_SAVE << "Could not parse file data into config\n";
351  throw game::load_game_failed();
352  }
353 }
354 
355 void save_index_class::delete_old_auto_saves(const int autosavemax, const int infinite_auto_saves)
356 {
357  log_scope("delete_old_auto_saves()");
358  if(read_only_) {
359  LOG_SAVE << "no-op: read_only instance";
360  return;
361  }
362 
363  const std::string auto_save = _("Auto-Save");
364 
365  int countdown = autosavemax;
366  if(countdown == infinite_auto_saves) {
367  return;
368  }
369 
370  std::vector<save_info> games = get_saves_list(&auto_save);
371  for(std::vector<save_info>::iterator i = games.begin(); i != games.end(); ++i) {
372  if(countdown-- <= 0) {
373  LOG_SAVE << "Deleting savegame '" << i->name() << "'\n";
374  delete_game(i->name());
375  }
376  }
377 }
378 
379 void save_index_class::delete_game(const std::string& name)
380 {
381  if(read_only_) {
382  log_scope("delete_game()");
383  LOG_SAVE << "no-op: read_only instance";
384  return;
385  }
386 
387  filesystem::delete_file(dir() + "/" + name);
388  remove(name);
389 }
390 
391 create_save_info::create_save_info(const std::shared_ptr<save_index_class>& manager)
392  : manager_(manager)
393 {
394 }
395 
396 save_info create_save_info::operator()(const std::string& filename) const
397 {
398  std::time_t modified = filesystem::file_modified_time(manager_->dir() + "/" + filename);
399  manager_->set_modified(filename, modified);
400  return save_info(filename, manager_, modified);
401 }
402 
403 void extract_summary_from_config(config& cfg_save, config& cfg_summary)
404 {
405  const config& cfg_snapshot = cfg_save.child("snapshot");
406 
407  // Servergenerated replays contain [scenario] and no [replay_start]
408  const config& cfg_replay_start = cfg_save.child("replay_start")
409  ? cfg_save.child("replay_start")
410  : cfg_save.child("scenario");
411 
412  const config& cfg_replay = cfg_save.child("replay");
413  const bool has_replay = cfg_replay && !cfg_replay.empty();
414  const bool has_snapshot = cfg_snapshot && cfg_snapshot.has_child("side");
415 
416  cfg_summary["replay"] = has_replay;
417  cfg_summary["snapshot"] = has_snapshot;
418 
419  cfg_summary["label"] = cfg_save["label"];
420  cfg_summary["campaign_type"] = cfg_save["campaign_type"];
421 
422  if(cfg_save.has_child("carryover_sides_start")) {
423  cfg_summary["scenario"] = cfg_save.child("carryover_sides_start")["next_scenario"];
424  } else {
425  cfg_summary["scenario"] = cfg_save["scenario"];
426  }
427 
428  cfg_summary["difficulty"] = cfg_save["difficulty"];
429  cfg_summary["random_mode"] = cfg_save["random_mode"];
430 
431  cfg_summary["active_mods"] = cfg_save.child_or_empty("multiplayer")["active_mods"];
432  cfg_summary["campaign"] = cfg_save["campaign"];
433  cfg_summary["version"] = cfg_save["version"];
434  cfg_summary["corrupt"] = "";
435 
436  if(has_snapshot) {
437  cfg_summary["turn"] = cfg_snapshot["turn_at"];
438  if(cfg_snapshot["turns"] != "-1") {
439  cfg_summary["turn"] = cfg_summary["turn"].str() + "/" + cfg_snapshot["turns"].str();
440  }
441  }
442 
443  // Ensure we don't get duplicate [leader] tags
444  cfg_summary.clear_children("leader");
445 
446  // Find the human leaders so we can display their icons and names in the load menu.
447  config leader_config;
448 
449  bool shrouded = false;
450 
451  if(const config& snapshot = *(has_snapshot ? &cfg_snapshot : &cfg_replay_start)) {
452  for(const config& side : snapshot.child_range("side")) {
453  std::string leader;
454  std::string leader_image;
455  std::string leader_image_tc_modifier;
456  std::string leader_name;
457  int gold = side["gold"];
458  int units = 0, recall_units = 0;
459 
460  if(side["controller"] != team::CONTROLLER::enum_to_string(team::CONTROLLER::HUMAN)) {
461  continue;
462  }
463 
464  if(side["shroud"].to_bool()) {
465  shrouded = true;
466  }
467 
468  for(const config& u : side.child_range("unit")) {
469  if(u.has_attribute("x") && u.has_attribute("y")) {
470  units++;
471  } else {
472  recall_units++;
473  }
474 
475  // Only take the first leader
476  if(!leader.empty() || !u["canrecruit"].to_bool()) {
477  continue;
478  }
479 
480  const std::string tc_color = team::get_side_color_id_from_config(side);
481 
482  // Don't count it among the troops
483  units--;
484  leader = u["id"].str();
485  leader_name = u["name"].str();
486  leader_image = u["image"].str();
487  leader_image_tc_modifier = "~RC(" + u["flag_rgb"].str() + ">" + tc_color + ")";
488  }
489 
490  // We need a binary path-independent path to the leader image here so it can be displayed
491  // for campaign-specific units even when the campaign isn't loaded yet.
492  std::string leader_image_path = filesystem::get_independent_binary_file_path("images", leader_image);
493 
494  // If the image path was found, we append the leader TC modifier. If it's not (such as in
495  // the case where the binary path hasn't been loaded yet, perhaps due to save_index being
496  // deleted), the unaltered image path is used and will be parsed by get_independent_binary_file_path
497  // at runtime.
498  if(!leader_image_path.empty()) {
499  leader_image_path += leader_image_tc_modifier;
500 
501  leader_image = leader_image_path;
502  }
503 
504  leader_config["leader"] = leader;
505  leader_config["leader_name"] = leader_name;
506  leader_config["leader_image"] = leader_image;
507  leader_config["leader_image_tc_modifier"] = leader_image_tc_modifier;
508  leader_config["gold"] = gold;
509  leader_config["units"] = units;
510  leader_config["recall_units"] = recall_units;
511 
512  cfg_summary.add_child("leader", leader_config);
513  }
514  }
515 
516  if(!shrouded) {
517  if(has_snapshot) {
518  if(!cfg_snapshot.find_child("side", "shroud", "yes") && cfg_snapshot.has_attribute("map_data")) {
519  cfg_summary["map_data"] = cfg_snapshot["map_data"].str();
520  } else {
521  ERR_SAVE << "Not saving map because there is shroud" << std::endl;
522  }
523  } else if(has_replay) {
524  if(!cfg_replay_start.find_child("side", "shroud", "yes") && cfg_replay_start.has_attribute("map_data")) {
525  cfg_summary["map_data"] = cfg_replay_start["map_data"];
526  } else {
527  ERR_SAVE << "Not saving map because there is shroud" << std::endl;
528  }
529  }
530  }
531 }
532 
533 } // end namespace savegame
bool clean_up_index_
Flag to only run the clean_up_index method once.
Definition: save_index.hpp:140
bool empty() const
Tests for an attribute that either was never set or was set to "".
int autosavemax()
Definition: game.cpp:805
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:402
void delete_game(const std::string &name)
Delete a savegame, including deleting the underlying file.
Definition: save_index.cpp:379
save_index_class(const std::string &dir)
Constructor for a read-only instance.
Definition: save_index.cpp:144
void rebuild(const std::string &name)
Definition: save_index.cpp:44
void clear_children(T... keys)
Definition: config.hpp:557
int dummy
Definition: lstrlib.cpp:1347
bool delete_file(const std::string &filename)
Definition: filesystem.cpp:987
static std::string get_side_color_id_from_config(const config &cfg)
Definition: team.cpp:1012
config & find_child(config_key_type key, const std::string &name, const std::string &value)
Returns the first child of tag key with a name attribute containing value.
Definition: config.cpp:885
Variant for storing WML attributes.
bool operator()(const save_info &a, const save_info &b) const
Definition: save_index.cpp:274
void extract_summary_from_config(config &, config &)
Definition: save_index.cpp:403
void read_bz2(config &cfg, std::istream &file, abstract_validator *validator)
Might throw a std::ios_base::failure especially bzip2_error.
Definition: parser.cpp:689
Error used when game loading fails.
Definition: game_errors.hpp:31
bool has_attribute(config_key_type key) const
Definition: config.cpp:211
#define a
static std::shared_ptr< save_index_class > default_saves_dir()
Returns an instance for managing saves in filesystem::get_saves_dir()
Definition: save_index.cpp:210
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:394
void remove(const std::string &name)
Delete a savegame from the index, without deleting the underlying file.
Definition: save_index.cpp:70
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:263
void write_save_index()
Sync to disk, no-op if read_only_ is set.
Definition: save_index.cpp:116
child_itors child_range(config_key_type key)
Definition: config.cpp:344
static lg::log_domain log_engine("engine")
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
const attribute_value * get(config_key_type key) const
Returns a pointer to the attribute with the given key or nullptr if it does not exist.
Definition: config.cpp:780
std::vector< save_info > get_saves_list(const std::string *filter=nullptr)
Get a list of available saves.
Definition: save_index.cpp:217
void set_modified(const std::string &name, const std::time_t &modified)
Definition: save_index.cpp:77
void clear()
Definition: config.cpp:920
#define d
Contains the exception interfaces used to signal completion of a scenario, campaign or turn...
const std::string dir_
Definition: save_index.hpp:133
void read_gz(config &cfg, std::istream &file, abstract_validator *validator)
Might throw a std::ios_base::failure especially a gzip_error.
Definition: parser.cpp:683
static std::string _(const char *str)
Definition: gettext.hpp:93
std::string get_saves_dir()
Definitions for the interface to Wesnoth Markup Language (WML).
void read_save_file(const std::string &dir, const std::string &name, config &cfg, std::string *error_log)
Read the complete config information out of a savefile.
Definition: save_index.cpp:314
filesystem::scoped_ostream ostream_file(const std::string &fname, std::ios_base::openmode mode, bool create_directory)
const config & summary() const
Definition: save_index.cpp:247
std::string get_independent_binary_file_path(const std::string &type, const std::string &filename)
Returns an asset path to filename for binary path-independent use in saved games. ...
std::shared_ptr< save_index_class > manager_
Definition: save_index.hpp:76
#define ERR_SAVE
Definition: save_index.cpp:35
#define b
std::string strftime(const std::string &format, const std::tm *time)
Definition: gettext.cpp:548
std::string format_time_local() const
Definition: save_index.cpp:252
create_for_default_saves_dir
Syntatic sugar for choosing which constructor to use.
Definition: save_index.hpp:87
void write(std::ostream &out, const configr_of &cfg, unsigned int level)
Definition: parser.cpp:764
void clean_up_index()
Deletes non-existent save files from the index.
Definition: save_index.cpp:100
void write_gz(std::ostream &out, const configr_of &cfg)
Definition: parser.cpp:783
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:627
bool read_only_
The instance for default_saves_dir() writes a cache file.
Definition: save_index.hpp:138
void get_files_in_dir(const std::string &dir, std::vector< std::string > *files, std::vector< std::string > *dirs, name_mode mode, filter_mode filter, reorder_mode reorder, file_tree_checksum *checksum)
Get a list of all files and/or directories in a given directory.
Definition: filesystem.cpp:349
unsigned all_children_count() const
Definition: config.cpp:384
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:39
std::time_t to_time_t(std::time_t def=0) const
const char * what() const noexcept
Definition: exceptions.hpp:36
const std::string & name() const
Definition: save_index.hpp:39
static filesystem::scoped_istream find_save_file(const std::string &dir, const std::string &name, const std::vector< std::string > &suffixes)
Definition: save_index.cpp:298
const std::time_t & modified() const
Definition: save_index.hpp:44
std::unique_ptr< std::ostream > scoped_ostream
Definition: filesystem.hpp:40
bool countdown()
Definition: game.cpp:615
bool is_gzip_file(const std::string &filename)
Returns true if the file ends with &#39;.gz&#39;.
config & get(const std::string &name)
Definition: save_index.cpp:82
std::size_t i
Definition: function.cpp:967
logger & err()
Definition: log.cpp:77
const std::string & dir() const
Definition: save_index.cpp:95
An exception object used when an IO error occurs.
Definition: filesystem.hpp:48
void delete_old_auto_saves(const int autosavemax, const int infinite_auto_saves)
Delete autosaves that are no longer needed (according to the autosave policy in the preferences)...
Definition: save_index.cpp:355
#define log_scope(description)
Definition: log.hpp:218
static void fix_leader_image_path(config &data)
Definition: save_index.cpp:200
Declarations for File-IO.
static int sort(lua_State *L)
Definition: ltablib.cpp:397
#define LOG_SAVE
Definition: save_index.cpp:34
save_info operator()(const std::string &filename) const
Definition: save_index.cpp:396
config & add_child(config_key_type key)
Definition: config.cpp:514
compression::format save_compression_format()
Definition: game.cpp:857
bool is_bzip2_file(const std::string &filename)
Returns true if the file ends with &#39;.bz2&#39;.
Filename and modification date for a file list.
Definition: save_index.hpp:26
create_save_info(const std::shared_ptr< save_index_class > &)
Definition: save_index.cpp:391
std::time_t file_modified_time(const std::string &fname)
Get the modification time of a file.
double t
Definition: astarsearch.cpp:65
std::map< std::string, std::time_t > modified_
Definition: save_index.hpp:132
std::string format_time_summary() const
Definition: save_index.cpp:268
Standard logging facilities (interface).
std::string message
Definition: exceptions.hpp:30
#define e
const config & child_or_empty(config_key_type key) const
Returns the first child with the given key, or an empty config if there is none.
Definition: config.cpp:465
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
static lg::log_domain log_enginerefac("enginerefac")
std::string get_save_index_file()
A structure for comparing to save_info objects based on their modified time.
Definition: save_index.hpp:63
std::string format_time_summary(std::time_t t)
bool use_twelve_hour_clock_format()
Definition: general.cpp:944
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
bool empty() const
Definition: config.cpp:941
void remove_children(config_key_type key, std::function< bool(const config &)> p)
Removes all children with tag key for which p returns true.
Definition: config.cpp:745