openMSX
SettingsConfig.cc
Go to the documentation of this file.
1 #include "SettingsConfig.hh"
2 #include "MSXException.hh"
3 #include "StringOp.hh"
4 #include "File.hh"
5 #include "FileContext.hh"
6 #include "FileException.hh"
7 #include "FileOperations.hh"
8 #include "MemBuffer.hh"
9 #include "CliComm.hh"
10 #include "HotKey.hh"
11 #include "CommandException.hh"
13 #include "TclObject.hh"
14 #include "XMLOutputStream.hh"
15 #include "outer.hh"
16 #include "rapidsax.hh"
17 #include "unreachable.hh"
18 
19 using std::string;
20 
21 namespace openmsx {
22 
24 {
25  void start(std::string_view tag);
26  void attribute(std::string_view name, std::string_view value);
27  void text(std::string_view txt);
28  void stop();
29  void doctype(std::string_view txt);
30 
31  // parse result
32  struct Setting {
33  std::string_view name;
34  std::string_view value;
35  };
36  std::vector<Setting> settings;
37  struct Bind {
38  std::string_view key;
39  std::string_view cmd;
40  bool repeat = false;
41  bool event = false;
42  };
43  std::vector<Bind> binds;
44  std::vector<std::string_view> unbinds;
45  std::string_view systemID;
46 
47  // parse state
48  unsigned unknownLevel = 0;
49  enum State {
51  TOP,
57  END
58  } state = START;
61  std::string_view currentUnbind;
62 };
63 
65  GlobalCommandController& globalCommandController,
66  HotKey& hotKey_)
67  : commandController(globalCommandController)
68  , saveSettingsCommand(commandController)
69  , loadSettingsCommand(commandController)
70  , settingsManager(globalCommandController)
71  , hotKey(hotKey_)
72  , mustSaveSettings(false)
73 {
74 }
75 
77 {
78  if (mustSaveSettings) {
79  try {
80  saveSetting();
81  } catch (FileException& e) {
82  commandController.getCliComm().printWarning(
83  "Auto-saving of settings failed: ", e.getMessage());
84  }
85  }
86 }
87 
89 {
90  string resolved = context.resolve(filename);
91 
92  MemBuffer<char> buf;
93  try {
94  File file(resolved);
95  auto size = file.getSize();
97  file.read(buf.data(), size);
98  buf[size] = 0;
99  } catch (FileException& e) {
100  throw MSXException("Failed to read settings file '", filename,
101  "': ", e.getMessage());
102  }
103  SettingsParser parser;
104  rapidsax::parse<0>(parser, buf.data());
105  if (parser.systemID != "settings.dtd") {
106  throw MSXException(
107  "Failed to parser settings file '", filename,
108  "': systemID doesn't match (expected 'settings.dtd' got '",
109  parser.systemID, "')\n"
110  "You're probably using an old incompatible file format.");
111  }
112 
113  settingValues.clear();
114  settingValues.reserve(unsigned(parser.settings.size()));
115  for (const auto& [name, value] : parser.settings) {
116  settingValues.emplace(name, value);
117  }
118 
119  hotKey.loadInit();
120  for (const auto& [key, cmd, repeat, event] : parser.binds) {
121  hotKey.loadBind(key, cmd, repeat, event);
122  }
123  for (const auto& key : parser.unbinds) {
124  hotKey.loadUnbind(key);
125  }
126 
128 
129  // only set saveName after file was successfully parsed
130  saveName = resolved;
131 }
132 
134 {
135  saveName = context.resolveCreate(filename);
136 }
137 
138 const std::string* SettingsConfig::getValueForSetting(std::string_view setting) const
139 {
140  return lookup(settingValues, setting);
141 }
142 
143 void SettingsConfig::setValueForSetting(std::string_view setting, std::string_view value)
144 {
145  settingValues.insert_or_assign(setting, value);
146 }
147 
149 {
150  settingValues.erase(setting);
151 }
152 
154 {
155  if (filename.empty()) filename = saveName;
156  if (filename.empty()) return;
157 
158  struct SettingsWriter {
159  SettingsWriter(std::string filename)
160  : file(filename, File::TRUNCATE)
161  {
162  std::string_view header =
163  "<!DOCTYPE settings SYSTEM 'settings.dtd'>\n";
164  write(header.data(), header.size());
165  }
166 
167  void write(const char* buf, size_t len) {
168  file.write(buf, len);
169  }
170  void write1(char c) {
171  file.write(&c, 1);
172  }
173  void check(bool condition) const {
174  assert(condition); (void)condition;
175  }
176 
177  private:
178  File file;
179  };
180 
181  SettingsWriter writer(filename);
182  XMLOutputStream xml(writer);
183  xml.begin("settings");
184  {
185  xml.begin("settings");
186  for (const auto& [name, value] : settingValues) {
187  xml.begin("setting");
188  xml.attribute("id", name);
189  xml.data(value);
190  xml.end("setting");
191  }
192  xml.end("settings");
193  }
194  {
195  hotKey.saveBindings(xml);
196  }
197  xml.end("settings");
198 }
199 
200 
201 // class SaveSettingsCommand
202 
203 SettingsConfig::SaveSettingsCommand::SaveSettingsCommand(
204  CommandController& commandController_)
205  : Command(commandController_, "save_settings")
206 {
207 }
208 
209 void SettingsConfig::SaveSettingsCommand::execute(
210  span<const TclObject> tokens, TclObject& /*result*/)
211 {
212  checkNumArgs(tokens, Between{1, 2}, Prefix{1}, "?filename?");
213  auto& settingsConfig = OUTER(SettingsConfig, saveSettingsCommand);
214  try {
215  switch (tokens.size()) {
216  case 1:
217  settingsConfig.saveSetting();
218  break;
219  case 2:
220  settingsConfig.saveSetting(FileOperations::expandTilde(
221  string(tokens[1].getString())));
222  break;
223  }
224  } catch (FileException& e) {
225  throw CommandException(std::move(e).getMessage());
226  }
227 }
228 
229 string SettingsConfig::SaveSettingsCommand::help(span<const TclObject> /*tokens*/) const
230 {
231  return "Save the current settings.";
232 }
233 
234 void SettingsConfig::SaveSettingsCommand::tabCompletion(std::vector<string>& tokens) const
235 {
236  if (tokens.size() == 2) {
237  completeFileName(tokens, systemFileContext());
238  }
239 }
240 
241 
242 // class LoadSettingsCommand
243 
244 SettingsConfig::LoadSettingsCommand::LoadSettingsCommand(
245  CommandController& commandController_)
246  : Command(commandController_, "load_settings")
247 {
248 }
249 
250 void SettingsConfig::LoadSettingsCommand::execute(
251  span<const TclObject> tokens, TclObject& /*result*/)
252 {
253  checkNumArgs(tokens, 2, "filename");
254  auto& settingsConfig = OUTER(SettingsConfig, loadSettingsCommand);
255  settingsConfig.loadSetting(systemFileContext(), tokens[1].getString());
256 }
257 
258 string SettingsConfig::LoadSettingsCommand::help(span<const TclObject> /*tokens*/) const
259 {
260  return "Load settings from given file.";
261 }
262 
263 void SettingsConfig::LoadSettingsCommand::tabCompletion(std::vector<string>& tokens) const
264 {
265  if (tokens.size() == 2) {
266  completeFileName(tokens, systemFileContext());
267  }
268 }
269 
270 
271 void SettingsParser::start(std::string_view tag)
272 {
273  if (unknownLevel) {
274  ++unknownLevel;
275  return;
276  }
277  switch (state) {
278  case START:
279  if (tag == "settings") {
280  state = TOP;
281  return;
282  }
283  throw MSXException("Expected <settings> as root tag.");
284  case TOP:
285  if (tag == "settings") {
286  state = SETTINGS;
287  return;
288  } else if (tag == "bindings") {
289  state = BINDINGS;
290  return;
291  }
292  break;
293  case SETTINGS:
294  if (tag == "setting") {
295  state = SETTING;
297  return;
298  }
299  break;
300  case BINDINGS:
301  if (tag == "bind") {
302  state = BIND;
303  currentBind = Bind{};
304  return;
305  } else if (tag == "unbind") {
306  state = UNBIND;
307  currentUnbind = std::string_view{};
308  return;
309  }
310  break;
311  case SETTING:
312  case BIND:
313  case UNBIND:
314  break;
315  case END:
316  throw MSXException("Unexpected opening tag: ", tag);
317  default:
318  UNREACHABLE;
319  }
320 
321  ++unknownLevel;
322 }
323 
324 void SettingsParser::attribute(std::string_view name, std::string_view value)
325 {
326  if (unknownLevel) return;
327 
328  switch (state) {
329  case SETTING:
330  if (name == "id") {
331  currentSetting.name = value;
332  }
333  break;
334  case BIND:
335  if (name == "key") {
336  currentBind.key = value;
337  } else if (name == "repeat") {
339  } else if (name == "event") {
341  }
342  break;
343  case UNBIND:
344  if (name == "key") {
345  currentUnbind = value;
346  }
347  break;
348  default:
349  break; //nothing
350  }
351 }
352 
353 void SettingsParser::text(std::string_view txt)
354 {
355  if (unknownLevel) return;
356 
357  switch (state) {
358  case SETTING:
359  currentSetting.value = txt;
360  break;
361  case BIND:
362  currentBind.cmd = txt;
363  break;
364  default:
365  break; //nothing
366  }
367 }
368 
370 {
371  if (unknownLevel) {
372  --unknownLevel;
373  return;
374  }
375 
376  switch (state) {
377  case TOP:
378  state = END;
379  break;
380  case SETTINGS:
381  state = TOP;
382  break;
383  case BINDINGS:
384  state = TOP;
385  break;
386  case SETTING:
387  if (!currentSetting.name.empty()) {
388  settings.push_back(currentSetting);
389  }
390  state = SETTINGS;
391  break;
392  case BIND:
393  if (!currentBind.key.empty()) {
394  binds.push_back(currentBind);
395  }
396  state = BINDINGS;
397  break;
398  case UNBIND:
399  if (!currentUnbind.empty()) {
400  unbinds.push_back(currentUnbind);
401  }
402  state = BINDINGS;
403  break;
404  case START:
405  case END:
406  throw MSXException("Unexpected closing tag");
407  default:
408  UNREACHABLE;
409  }
410 }
411 
412 void SettingsParser::doctype(std::string_view txt)
413 {
414  auto pos1 = txt.find(" SYSTEM ");
415  if (pos1 == std::string_view::npos) return;
416  if ((pos1 + 8) >= txt.size()) return;
417  char q = txt[pos1 + 8];
418  if (q != one_of('"', '\'')) return;
419  auto t = txt.substr(pos1 + 9);
420  auto pos2 = t.find(q);
421  if (pos2 == std::string_view::npos) return;
422 
423  systemID = t.substr(0, pos2);
424 }
425 
426 } // namespace openmsx
BaseSetting * setting
Definition: Interpreter.cc:27
TclObject t
'XMLOutputStream' is a helper to write an XML file in a streaming way.
void attribute(std::string_view name, std::string_view value)
void begin(std::string_view tag)
void end(std::string_view tag)
void data(std::string_view value)
std::pair< iterator, bool > insert_or_assign(K &&key, V &&value)
Definition: hash_map.hh:99
void clear()
Definition: hash_set.hh:542
void reserve(unsigned count)
Definition: hash_set.hh:614
std::pair< iterator, bool > emplace(Args &&... args)
Definition: hash_set.hh:470
bool erase(const K &key)
Definition: hash_set.hh:491
Definition: one_of.hh:7
void printWarning(std::string_view message)
Definition: CliComm.cc:10
virtual CliComm & getCliComm()=0
void read(void *buffer, size_t num)
Read from file.
Definition: File.cc:91
size_t getSize()
Returns the size of this file.
Definition: File.cc:111
@ TRUNCATE
Definition: File.hh:20
void loadUnbind(std::string_view key)
Definition: HotKey.cc:189
void saveBindings(XmlStream &xml) const
Definition: HotKey.hh:47
void loadBind(std::string_view key, std::string_view cmd, bool repeat, bool event)
Definition: HotKey.cc:183
void loadInit()
Definition: HotKey.cc:175
const std::string & getMessage() const &
Definition: MSXException.hh:23
const T * data() const
Returns pointer to the start of the memory buffer.
Definition: MemBuffer.hh:81
void resize(size_t size)
Grow or shrink the memory block.
Definition: MemBuffer.hh:111
SettingsConfig(GlobalCommandController &globalCommandController, HotKey &hotKey)
SettingsManager & getSettingsManager()
void saveSetting(std::string filename={})
void loadSetting(const FileContext &context, std::string_view filename)
void setSaveFilename(const FileContext &context, std::string_view filename)
void removeValueForSetting(std::string_view setting)
void setValueForSetting(std::string_view setting, std::string_view value)
const std::string * getValueForSetting(std::string_view setting) const
void loadSettings(const SettingsConfig &config)
Definition: span.hh:126
constexpr index_type size() const noexcept
Definition: span.hh:296
const Value * lookup(const hash_map< Key, Value, Hasher, Equal > &map, const Key2 &key)
Definition: hash_map.hh:118
bool stringToBool(string_view str)
Definition: StringOp.cc:12
std::optional< Context > context
Definition: GLContext.cc:9
string expandTilde(string path)
Expand the '~' character to the users home directory.
This file implemented 3 utility functions:
Definition: Autofire.cc:9
const FileContext & systemFileContext()
Definition: FileContext.cc:157
constexpr const char *const filename
constexpr size_t EXTRA_BUFFER_SPACE
Definition: rapidsax.hh:41
size_t size(std::string_view utf8)
#define OUTER(type, member)
Definition: outer.hh:41
std::vector< Bind > binds
void start(std::string_view tag)
std::string_view systemID
enum openmsx::SettingsParser::State state
std::vector< Setting > settings
void attribute(std::string_view name, std::string_view value)
std::vector< std::string_view > unbinds
void text(std::string_view txt)
void doctype(std::string_view txt)
std::string_view currentUnbind
#define UNREACHABLE
Definition: unreachable.hh:38
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.
Definition: xrange.hh:170