3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #ifndef SETTINGS_HEADER
21 #define SETTINGS_HEADER
23 #include "irrlichttypes_bloated.h"
24 #include "exceptions.h"
26 #include "jthread/jmutex.h"
27 #include "jthread/jmutexautolock.h"
34 #include "util/string.h"
44 VALUETYPE_FLAG // Doesn't take any arguments
49 ValueSpec(ValueType a_type, const char *a_help=NULL)
65 void writeLines(std::ostream &os)
67 JMutexAutoLock lock(m_mutex);
69 for(std::map<std::string, std::string>::iterator
70 i = m_settings.begin();
71 i != m_settings.end(); ++i)
73 std::string name = i->first;
74 std::string value = i->second;
75 os<<name<<" = "<<value<<"\n";
79 // return all keys used
80 std::vector<std::string> getNames(){
81 std::vector<std::string> names;
82 for(std::map<std::string, std::string>::iterator
83 i = m_settings.begin();
84 i != m_settings.end(); ++i)
86 names.push_back(i->first);
92 bool remove(const std::string& name)
94 return m_settings.erase(name);
98 bool parseConfigLine(const std::string &line)
100 JMutexAutoLock lock(m_mutex);
102 std::string trimmedline = trim(line);
104 // Ignore empty lines and comments
105 if(trimmedline.size() == 0 || trimmedline[0] == '#')
108 //infostream<<"trimmedline=\""<<trimmedline<<"\""<<std::endl;
110 Strfnd sf(trim(line));
112 std::string name = sf.next("=");
118 std::string value = sf.next("\n");
121 /*infostream<<"Config name=\""<<name<<"\" value=\""
122 <<value<<"\""<<std::endl;*/
124 m_settings[name] = value;
129 void parseConfigLines(std::istream &is, const std::string &endstring)
135 std::getline(is, line);
136 std::string trimmedline = trim(line);
138 if(trimmedline == endstring)
141 parseConfigLine(line);
145 // Returns false on EOF
146 bool parseConfigObject(std::istream &is)
152 NOTE: This function might be expanded to allow multi-line
156 std::getline(is, line);
157 //infostream<<"got line: \""<<line<<"\""<<std::endl;
159 return parseConfigLine(line);
163 Read configuration file
165 Returns true on success
167 bool readConfigFile(const char *filename)
169 std::ifstream is(filename);
170 if(is.good() == false)
173 /*infostream<<"Parsing configuration file: \""
174 <<filename<<"\""<<std::endl;*/
176 while(parseConfigObject(is));
182 Reads a configuration object from stream (usually a single line)
185 Preserves comments and empty lines.
187 Settings that were added to dst are also added to updated.
188 key of updated is setting name, value of updated is dummy.
192 bool getUpdatedConfigObject(std::istream &is,
193 std::list<std::string> &dst,
194 std::set<std::string> &updated,
197 JMutexAutoLock lock(m_mutex);
202 // NOTE: This function will be expanded to allow multi-line settings
204 std::getline(is, line);
206 std::string trimmedline = trim(line);
208 std::string line_end = "";
209 if(is.eof() == false)
212 // Ignore empty lines and comments
213 if(trimmedline.size() == 0 || trimmedline[0] == '#')
215 dst.push_back(line+line_end);
219 Strfnd sf(trim(line));
221 std::string name = sf.next("=");
226 dst.push_back(line+line_end);
230 std::string value = sf.next("\n");
233 if(m_settings.find(name) != m_settings.end())
235 std::string newvalue = m_settings[name];
237 if(newvalue != value)
239 infostream<<"Changing value of \""<<name<<"\" = \""
240 <<value<<"\" -> \""<<newvalue<<"\""
242 value_changed = true;
245 dst.push_back(name + " = " + newvalue + line_end);
247 updated.insert(name);
249 else //file contains a setting which is not in m_settings
256 Updates configuration file
258 Returns true on success
260 bool updateConfigFile(const char *filename)
262 infostream<<"Updating configuration file: \""
263 <<filename<<"\""<<std::endl;
265 std::list<std::string> objects;
266 std::set<std::string> updated;
267 bool something_actually_changed = false;
269 // Read and modify stuff
271 std::ifstream is(filename);
272 if(is.good() == false)
274 infostream<<"updateConfigFile():"
275 " Error opening configuration file"
277 <<filename<<"\""<<std::endl;
281 while(getUpdatedConfigObject(is, objects, updated,
282 something_actually_changed));
286 JMutexAutoLock lock(m_mutex);
288 // If something not yet determined to have been changed, check if
289 // any new stuff was added
290 if(!something_actually_changed){
291 for(std::map<std::string, std::string>::iterator
292 i = m_settings.begin();
293 i != m_settings.end(); ++i)
295 if(updated.find(i->first) != updated.end())
297 something_actually_changed = true;
302 // If nothing was actually changed, skip writing the file
303 if(!something_actually_changed){
304 infostream<<"Skipping writing of "<<filename
305 <<" because content wouldn't be modified"<<std::endl;
311 std::ostringstream ss(std::ios_base::binary);
316 for(std::list<std::string>::iterator
318 i != objects.end(); ++i)
324 Write stuff that was not already in the file
326 for(std::map<std::string, std::string>::iterator
327 i = m_settings.begin();
328 i != m_settings.end(); ++i)
330 if(updated.find(i->first) != updated.end())
332 std::string name = i->first;
333 std::string value = i->second;
334 infostream<<"Adding \""<<name<<"\" = \""<<value<<"\""
336 ss<<name<<" = "<<value<<"\n";
339 if(!fs::safeWriteToFile(filename, ss.str()))
341 errorstream<<"Error writing configuration file: \""
342 <<filename<<"\""<<std::endl;
351 NOTE: Types of allowed_options are ignored
353 returns true on success
355 bool parseCommandLine(int argc, char *argv[],
356 std::map<std::string, ValueSpec> &allowed_options)
358 int nonopt_index = 0;
364 std::string argname = argv[i];
365 if(argname.substr(0, 2) != "--")
367 // If option doesn't start with -, read it in as nonoptX
368 if(argname[0] != '-'){
369 std::string name = "nonopt";
370 name += itos(nonopt_index);
376 errorstream<<"Invalid command-line parameter \""
377 <<argname<<"\": --<option> expected."<<std::endl;
382 std::string name = argname.substr(2);
384 std::map<std::string, ValueSpec>::iterator n;
385 n = allowed_options.find(name);
386 if(n == allowed_options.end())
388 errorstream<<"Unknown command-line parameter \""
389 <<argname<<"\""<<std::endl;
393 ValueType type = n->second.type;
395 std::string value = "";
397 if(type == VALUETYPE_FLAG)
405 errorstream<<"Invalid command-line parameter \""
406 <<name<<"\": missing value"<<std::endl;
414 infostream<<"Valid command-line parameter: \""
415 <<name<<"\" = \""<<value<<"\""
423 void set(std::string name, std::string value)
425 JMutexAutoLock lock(m_mutex);
427 m_settings[name] = value;
430 void set(std::string name, const char *value)
432 JMutexAutoLock lock(m_mutex);
434 m_settings[name] = value;
438 void setDefault(std::string name, std::string value)
440 JMutexAutoLock lock(m_mutex);
442 m_defaults[name] = value;
445 bool exists(std::string name)
447 JMutexAutoLock lock(m_mutex);
449 return (m_settings.find(name) != m_settings.end() || m_defaults.find(name) != m_defaults.end());
452 std::string get(std::string name)
454 JMutexAutoLock lock(m_mutex);
456 std::map<std::string, std::string>::iterator n;
457 n = m_settings.find(name);
458 if(n == m_settings.end())
460 n = m_defaults.find(name);
461 if(n == m_defaults.end())
463 throw SettingNotFoundException(("Setting [" + name + "] not found ").c_str());
470 //////////// Get setting
471 bool getBool(std::string name)
473 return is_yes(get(name));
476 bool getFlag(std::string name)
480 return getBool(name);
482 catch(SettingNotFoundException &e)
489 bool getBoolAsk(std::string name, std::string question, bool def)
491 // If it is in settings
493 return getBool(name);
497 std::cout<<question<<" [y/N]: ";
498 std::cin.getline(templine, 10);
507 float getFloat(std::string name)
509 return stof(get(name));
512 u16 getU16(std::string name)
514 return stoi(get(name), 0, 65535);
517 u16 getU16Ask(std::string name, std::string question, u16 def)
519 // If it is in settings
525 std::cout<<question<<" ["<<def<<"]: ";
526 std::cin.getline(templine, 10);
532 return stoi(s, 0, 65535);
535 s16 getS16(std::string name)
537 return stoi(get(name), -32768, 32767);
540 s32 getS32(std::string name)
542 return stoi(get(name));
545 v3f getV3F(std::string name)
550 value.X = stof(f.next(","));
551 value.Y = stof(f.next(","));
552 value.Z = stof(f.next(")"));
556 v2f getV2F(std::string name)
561 value.X = stof(f.next(","));
562 value.Y = stof(f.next(")"));
566 u64 getU64(std::string name)
569 std::string s = get(name);
570 std::istringstream ss(s);
575 u32 getFlagStr(std::string name, FlagDesc *flagdesc, u32 *flagmask)
577 std::string val = get(name);
578 return (isdigit(val[0])) ? stoi(val) :
579 readFlagString(val, flagdesc, flagmask);
582 // N.B. if getStruct() is used to read a non-POD aggregate type,
583 // the behavior is undefined.
584 bool getStruct(std::string name, std::string format, void *out, size_t olen)
587 std::vector<std::string *> strs_alloced;
588 std::string *str, valstr;
594 } catch (SettingNotFoundException &e) {
598 char *s = &valstr[0];
599 char *buf = new char[len];
602 char *fmtpos, *fmt = &format[0];
603 while ((f = strtok_r(fmt, ",", &fmtpos)) && s) {
606 bool is_unsigned = false;
610 width = (int)strtol(f + 1, &f, 10);
611 if (width && valtype == 's')
620 bufpos += PADDING(bufpos, u16);
621 if ((bufpos - buf) + sizeof(u16) <= len) {
623 *(u16 *)bufpos = (u16)strtoul(s, &s, 10);
625 *(s16 *)bufpos = (s16)strtol(s, &s, 10);
627 bufpos += sizeof(u16);
628 } else if (width == 32) {
629 bufpos += PADDING(bufpos, u32);
630 if ((bufpos - buf) + sizeof(u32) <= len) {
632 *(u32 *)bufpos = (u32)strtoul(s, &s, 10);
634 *(s32 *)bufpos = (s32)strtol(s, &s, 10);
636 bufpos += sizeof(u32);
637 } else if (width == 64) {
638 bufpos += PADDING(bufpos, u64);
639 if ((bufpos - buf) + sizeof(u64) <= len) {
641 *(u64 *)bufpos = (u64)strtoull(s, &s, 10);
643 *(s64 *)bufpos = (s64)strtoll(s, &s, 10);
645 bufpos += sizeof(u64);
650 snext = strchr(s, ',');
654 bufpos += PADDING(bufpos, bool);
655 if ((bufpos - buf) + sizeof(bool) <= len)
656 *(bool *)bufpos = is_yes(std::string(s));
657 bufpos += sizeof(bool);
662 bufpos += PADDING(bufpos, float);
663 if ((bufpos - buf) + sizeof(float) <= len)
664 *(float *)bufpos = strtof(s, &s);
665 bufpos += sizeof(float);
670 while (*s == ' ' || *s == '\t')
672 if (*s++ != '"') //error, expected string
676 while (snext[0] && !(snext[-1] != '\\' && snext[0] == '"'))
680 bufpos += PADDING(bufpos, std::string *);
682 str = new std::string(s);
684 while ((pos = str->find("\\\"", pos)) != std::string::npos)
687 if ((bufpos - buf) + sizeof(std::string *) <= len)
688 *(std::string **)bufpos = str;
689 bufpos += sizeof(std::string *);
690 strs_alloced.push_back(str);
692 s = *snext ? snext + 1 : NULL;
695 while (*s == ' ' || *s == '\t')
697 if (*s++ != '(') //error, expected vector
701 bufpos += PADDING(bufpos, v2f);
703 if ((bufpos - buf) + sizeof(v2f) <= len) {
704 v2f *v = (v2f *)bufpos;
705 v->X = strtof(s, &s);
707 v->Y = strtof(s, &s);
710 bufpos += sizeof(v2f);
711 } else if (width == 3) {
712 bufpos += PADDING(bufpos, v3f);
713 if ((bufpos - buf) + sizeof(v3f) <= len) {
714 v3f *v = (v3f *)bufpos;
715 v->X = strtof(s, &s);
717 v->Y = strtof(s, &s);
719 v->Z = strtof(s, &s);
722 bufpos += sizeof(v3f);
726 default: //error, invalid format specifier
733 if ((size_t)(bufpos - buf) > len) //error, buffer too small
737 if (f && *f) { //error, mismatched number of fields and values
739 for (size_t i = 0; i != strs_alloced.size(); i++)
740 delete strs_alloced[i];
745 memcpy(out, buf, olen);
750 //////////// Try to get value, no exception thrown
751 bool getNoEx(std::string name, std::string &val)
756 } catch (SettingNotFoundException &e) {
761 // N.B. getFlagStrNoEx() does not set val, but merely modifies it. Thus,
762 // val must be initialized before using getFlagStrNoEx(). The intention of
763 // this is to simplify modifying a flags field from a default value.
764 bool getFlagStrNoEx(std::string name, u32 &val, FlagDesc *flagdesc)
769 flags = getFlagStr(name, flagdesc, &flagmask);
775 } catch (SettingNotFoundException &e) {
780 bool getFloatNoEx(std::string name, float &val)
783 val = getFloat(name);
785 } catch (SettingNotFoundException &e) {
790 bool getU16NoEx(std::string name, int &val)
795 } catch (SettingNotFoundException &e) {
800 bool getU16NoEx(std::string name, u16 &val)
805 } catch (SettingNotFoundException &e) {
810 bool getS16NoEx(std::string name, int &val)
815 } catch (SettingNotFoundException &e) {
820 bool getS16NoEx(std::string name, s16 &val)
825 } catch (SettingNotFoundException &e) {
830 bool getS32NoEx(std::string name, s32 &val)
835 } catch (SettingNotFoundException &e) {
840 bool getV3FNoEx(std::string name, v3f &val)
845 } catch (SettingNotFoundException &e) {
850 bool getV2FNoEx(std::string name, v2f &val)
855 } catch (SettingNotFoundException &e) {
860 bool getU64NoEx(std::string name, u64 &val)
865 } catch (SettingNotFoundException &e) {
870 //////////// Set setting
872 // N.B. if setStruct() is used to write a non-POD aggregate type,
873 // the behavior is undefined.
874 bool setStruct(std::string name, std::string format, void *value)
877 int sbuflen = sizeof(sbuf) - 1;
884 char *bufpos = (char *)value;
885 char *fmtpos, *fmt = &format[0];
886 while ((f = strtok_r(fmt, ",", &fmtpos))) {
888 bool is_unsigned = false;
889 int width = 0, nprinted = 0;
892 width = (int)strtol(f + 1, &f, 10);
893 if (width && valtype == 's')
902 bufpos += PADDING(bufpos, u16);
903 nprinted = snprintf(sbuf + pos, sbuflen,
904 is_unsigned ? "%u, " : "%d, ",
906 bufpos += sizeof(u16);
907 } else if (width == 32) {
908 bufpos += PADDING(bufpos, u32);
909 nprinted = snprintf(sbuf + pos, sbuflen,
910 is_unsigned ? "%u, " : "%d, ",
912 bufpos += sizeof(u32);
913 } else if (width == 64) {
914 bufpos += PADDING(bufpos, u64);
915 nprinted = snprintf(sbuf + pos, sbuflen,
916 is_unsigned ? "%llu, " : "%lli, ",
917 (unsigned long long)*((u64 *)bufpos));
918 bufpos += sizeof(u64);
922 bufpos += PADDING(bufpos, bool);
923 nprinted = snprintf(sbuf + pos, sbuflen, "%s, ",
924 *((bool *)bufpos) ? "true" : "false");
925 bufpos += sizeof(bool);
928 bufpos += PADDING(bufpos, float);
929 nprinted = snprintf(sbuf + pos, sbuflen, "%f, ",
931 bufpos += sizeof(float);
934 bufpos += PADDING(bufpos, std::string *);
935 str = **((std::string **)bufpos);
938 while ((fpos = str.find('"', fpos)) != std::string::npos) {
939 str.insert(fpos, 1, '\\');
943 nprinted = snprintf(sbuf + pos, sbuflen, "\"%s\", ",
944 (*((std::string **)bufpos))->c_str());
945 bufpos += sizeof(std::string *);
949 bufpos += PADDING(bufpos, v2f);
950 v2f *v = (v2f *)bufpos;
951 nprinted = snprintf(sbuf + pos, sbuflen,
952 "(%f, %f), ", v->X, v->Y);
953 bufpos += sizeof(v2f);
955 bufpos += PADDING(bufpos, v3f);
956 v3f *v = (v3f *)bufpos;
957 nprinted = snprintf(sbuf + pos, sbuflen,
958 "(%f, %f, %f), ", v->X, v->Y, v->Z);
959 bufpos += sizeof(v3f);
965 if (nprinted < 0) //error, buffer too small
974 set(name, std::string(sbuf));
978 void setFlagStr(std::string name, u32 flags,
979 FlagDesc *flagdesc, u32 flagmask)
981 set(name, writeFlagString(flags, flagdesc, flagmask));
984 void setBool(std::string name, bool value)
992 void setFloat(std::string name, float value)
994 set(name, ftos(value));
997 void setV3F(std::string name, v3f value)
999 std::ostringstream os;
1000 os<<"("<<value.X<<","<<value.Y<<","<<value.Z<<")";
1001 set(name, os.str());
1004 void setV2F(std::string name, v2f value)
1006 std::ostringstream os;
1007 os<<"("<<value.X<<","<<value.Y<<")";
1008 set(name, os.str());
1011 void setS16(std::string name, s16 value)
1013 set(name, itos(value));
1016 void setS32(std::string name, s32 value)
1018 set(name, itos(value));
1021 void setU64(std::string name, u64 value)
1023 std::ostringstream os;
1025 set(name, os.str());
1030 JMutexAutoLock lock(m_mutex);
1036 void updateValue(Settings &other, const std::string &name)
1038 JMutexAutoLock lock(m_mutex);
1044 std::string val = other.get(name);
1045 m_settings[name] = val;
1046 } catch(SettingNotFoundException &e){
1052 void update(Settings &other)
1054 JMutexAutoLock lock(m_mutex);
1055 JMutexAutoLock lock2(other.m_mutex);
1060 m_settings.insert(other.m_settings.begin(), other.m_settings.end());
1061 m_defaults.insert(other.m_defaults.begin(), other.m_defaults.end());
1066 Settings & operator+=(Settings &other)
1068 JMutexAutoLock lock(m_mutex);
1069 JMutexAutoLock lock2(other.m_mutex);
1080 Settings & operator=(Settings &other)
1082 JMutexAutoLock lock(m_mutex);
1083 JMutexAutoLock lock2(other.m_mutex);
1095 std::map<std::string, std::string> m_settings;
1096 std::map<std::string, std::string> m_defaults;
1097 // All methods that access m_settings/m_defaults directly should lock this.