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"
27 #include <jmutexautolock.h>
34 #include "util/string.h"
40 VALUETYPE_FLAG // Doesn't take any arguments
45 ValueSpec(ValueType a_type, const char *a_help=NULL)
62 void writeLines(std::ostream &os)
64 JMutexAutoLock lock(m_mutex);
66 for(core::map<std::string, std::string>::Iterator
67 i = m_settings.getIterator();
68 i.atEnd() == false; i++)
70 std::string name = i.getNode()->getKey();
71 std::string value = i.getNode()->getValue();
72 os<<name<<" = "<<value<<"\n";
76 // return all keys used
77 std::vector<std::string> getNames(){
78 std::vector<std::string> names;
79 for(core::map<std::string, std::string>::Iterator
80 i = m_settings.getIterator();
81 i.atEnd() == false; i++)
83 std::string name = i.getNode()->getKey();
84 names.push_back(name);
90 bool remove(const std::string& name)
92 return m_settings.remove(name);
96 bool parseConfigLine(const std::string &line)
98 JMutexAutoLock lock(m_mutex);
100 std::string trimmedline = trim(line);
102 // Ignore empty lines and comments
103 if(trimmedline.size() == 0 || trimmedline[0] == '#')
106 //infostream<<"trimmedline=\""<<trimmedline<<"\""<<std::endl;
108 Strfnd sf(trim(line));
110 std::string name = sf.next("=");
116 std::string value = sf.next("\n");
119 /*infostream<<"Config name=\""<<name<<"\" value=\""
120 <<value<<"\""<<std::endl;*/
122 m_settings[name] = value;
127 void parseConfigLines(std::istream &is, const std::string &endstring)
133 std::getline(is, line);
134 std::string trimmedline = trim(line);
136 if(trimmedline == endstring)
139 parseConfigLine(line);
143 // Returns false on EOF
144 bool parseConfigObject(std::istream &is)
150 NOTE: This function might be expanded to allow multi-line
154 std::getline(is, line);
155 //infostream<<"got line: \""<<line<<"\""<<std::endl;
157 return parseConfigLine(line);
161 Read configuration file
163 Returns true on success
165 bool readConfigFile(const char *filename)
167 std::ifstream is(filename);
168 if(is.good() == false)
171 /*infostream<<"Parsing configuration file: \""
172 <<filename<<"\""<<std::endl;*/
174 while(parseConfigObject(is));
180 Reads a configuration object from stream (usually a single line)
183 Preserves comments and empty lines.
185 Settings that were added to dst are also added to updated.
186 key of updated is setting name, value of updated is dummy.
190 bool getUpdatedConfigObject(std::istream &is,
191 core::list<std::string> &dst,
192 core::map<std::string, bool> &updated,
195 JMutexAutoLock lock(m_mutex);
200 // NOTE: This function will be expanded to allow multi-line settings
202 std::getline(is, line);
204 std::string trimmedline = trim(line);
206 std::string line_end = "";
207 if(is.eof() == false)
210 // Ignore empty lines and comments
211 if(trimmedline.size() == 0 || trimmedline[0] == '#')
213 dst.push_back(line+line_end);
217 Strfnd sf(trim(line));
219 std::string name = sf.next("=");
224 dst.push_back(line+line_end);
228 std::string value = sf.next("\n");
231 if(m_settings.find(name))
233 std::string newvalue = m_settings[name];
235 if(newvalue != value)
237 infostream<<"Changing value of \""<<name<<"\" = \""
238 <<value<<"\" -> \""<<newvalue<<"\""
240 value_changed = true;
243 dst.push_back(name + " = " + newvalue + line_end);
245 updated[name] = true;
247 else //file contains a setting which is not in m_settings
254 Updates configuration file
256 Returns true on success
258 bool updateConfigFile(const char *filename)
260 infostream<<"Updating configuration file: \""
261 <<filename<<"\""<<std::endl;
263 core::list<std::string> objects;
264 core::map<std::string, bool> updated;
265 bool something_actually_changed = false;
267 // Read and modify stuff
269 std::ifstream is(filename);
270 if(is.good() == false)
272 infostream<<"updateConfigFile():"
273 " Error opening configuration file"
275 <<filename<<"\""<<std::endl;
279 while(getUpdatedConfigObject(is, objects, updated,
280 something_actually_changed));
284 JMutexAutoLock lock(m_mutex);
286 // If something not yet determined to have been changed, check if
287 // any new stuff was added
288 if(!something_actually_changed){
289 for(core::map<std::string, std::string>::Iterator
290 i = m_settings.getIterator();
291 i.atEnd() == false; i++)
293 if(updated.find(i.getNode()->getKey()))
295 something_actually_changed = true;
300 // If nothing was actually changed, skip writing the file
301 if(!something_actually_changed){
302 infostream<<"Skipping writing of "<<filename
303 <<" because content wouldn't be modified"<<std::endl;
309 std::ofstream os(filename);
310 if(os.good() == false)
312 errorstream<<"Error opening configuration file"
314 <<filename<<"\""<<std::endl;
321 for(core::list<std::string>::Iterator
323 i != objects.end(); i++)
329 Write stuff that was not already in the file
331 for(core::map<std::string, std::string>::Iterator
332 i = m_settings.getIterator();
333 i.atEnd() == false; i++)
335 if(updated.find(i.getNode()->getKey()))
337 std::string name = i.getNode()->getKey();
338 std::string value = i.getNode()->getValue();
339 infostream<<"Adding \""<<name<<"\" = \""<<value<<"\""
341 os<<name<<" = "<<value<<"\n";
349 NOTE: Types of allowed_options are ignored
351 returns true on success
353 bool parseCommandLine(int argc, char *argv[],
354 core::map<std::string, ValueSpec> &allowed_options)
356 int nonopt_index = 0;
362 std::string argname = argv[i];
363 if(argname.substr(0, 2) != "--")
365 // If option doesn't start with -, read it in as nonoptX
366 if(argname[0] != '-'){
367 std::string name = "nonopt";
368 name += itos(nonopt_index);
374 errorstream<<"Invalid command-line parameter \""
375 <<argname<<"\": --<option> expected."<<std::endl;
380 std::string name = argname.substr(2);
382 core::map<std::string, ValueSpec>::Node *n;
383 n = allowed_options.find(name);
386 errorstream<<"Unknown command-line parameter \""
387 <<argname<<"\""<<std::endl;
391 ValueType type = n->getValue().type;
393 std::string value = "";
395 if(type == VALUETYPE_FLAG)
403 errorstream<<"Invalid command-line parameter \""
404 <<name<<"\": missing value"<<std::endl;
412 infostream<<"Valid command-line parameter: \""
413 <<name<<"\" = \""<<value<<"\""
421 void set(std::string name, std::string value)
423 JMutexAutoLock lock(m_mutex);
425 m_settings[name] = value;
428 void set(std::string name, const char *value)
430 JMutexAutoLock lock(m_mutex);
432 m_settings[name] = value;
436 void setDefault(std::string name, std::string value)
438 JMutexAutoLock lock(m_mutex);
440 m_defaults[name] = value;
443 bool exists(std::string name)
445 JMutexAutoLock lock(m_mutex);
447 return (m_settings.find(name) || m_defaults.find(name));
450 std::string get(std::string name)
452 JMutexAutoLock lock(m_mutex);
454 core::map<std::string, std::string>::Node *n;
455 n = m_settings.find(name);
458 n = m_defaults.find(name);
461 throw SettingNotFoundException("Setting not found");
465 return n->getValue();
468 bool getBool(std::string name)
470 return is_yes(get(name));
473 bool getFlag(std::string name)
477 return getBool(name);
479 catch(SettingNotFoundException &e)
486 bool getBoolAsk(std::string name, std::string question, bool def)
488 // If it is in settings
490 return getBool(name);
494 std::cout<<question<<" [y/N]: ";
495 std::cin.getline(templine, 10);
504 float getFloat(std::string name)
506 return stof(get(name));
509 u16 getU16(std::string name)
511 return stoi(get(name), 0, 65535);
514 u16 getU16Ask(std::string name, std::string question, u16 def)
516 // If it is in settings
522 std::cout<<question<<" ["<<def<<"]: ";
523 std::cin.getline(templine, 10);
529 return stoi(s, 0, 65535);
532 s16 getS16(std::string name)
534 return stoi(get(name), -32768, 32767);
537 s32 getS32(std::string name)
539 return stoi(get(name));
542 v3f getV3F(std::string name)
547 value.X = stof(f.next(","));
548 value.Y = stof(f.next(","));
549 value.Z = stof(f.next(")"));
553 v2f getV2F(std::string name)
558 value.X = stof(f.next(","));
559 value.Y = stof(f.next(")"));
563 u64 getU64(std::string name)
566 std::string s = get(name);
567 std::istringstream ss(s);
572 u32 getFlagStr(std::string name, FlagDesc *flagdesc)
574 std::string val = get(name);
575 return (isdigit(val[0])) ? stoi(val) : readFlagString(val, flagdesc);
578 template <class T> T *getStruct(std::string name, std::string format)
580 size_t len = sizeof(T);
581 std::vector<std::string *> strs_alloced;
583 std::string valstr = get(name);
584 char *s = &valstr[0];
586 char *bufpos = (char *)buf;
590 char *fmtpos, *fmt = &format[0];
591 while ((f = strtok_r(fmt, ",", &fmtpos)) && s) {
594 bool is_unsigned = false;
598 width = (int)strtol(f + 1, &f, 10);
599 if (width && valtype == 's')
608 bufpos += PADDING(bufpos, u16);
609 if ((bufpos - (char *)buf) + sizeof(u16) <= len) {
611 *(u16 *)bufpos = (u16)strtoul(s, &s, 10);
613 *(s16 *)bufpos = (s16)strtol(s, &s, 10);
615 bufpos += sizeof(u16);
616 } else if (width == 32) {
617 bufpos += PADDING(bufpos, u32);
618 if ((bufpos - (char *)buf) + sizeof(u32) <= len) {
620 *(u32 *)bufpos = (u32)strtoul(s, &s, 10);
622 *(s32 *)bufpos = (s32)strtol(s, &s, 10);
624 bufpos += sizeof(u32);
625 } else if (width == 64) {
626 bufpos += PADDING(bufpos, u64);
627 if ((bufpos - (char *)buf) + sizeof(u64) <= len) {
629 *(u64 *)bufpos = (u64)strtoull(s, &s, 10);
631 *(s64 *)bufpos = (s64)strtoll(s, &s, 10);
633 bufpos += sizeof(u64);
638 snext = strchr(s, ',');
642 bufpos += PADDING(bufpos, bool);
643 if ((bufpos - (char *)buf) + sizeof(bool) <= len)
644 *(bool *)bufpos = is_yes(std::string(s));
645 bufpos += sizeof(bool);
650 bufpos += PADDING(bufpos, float);
651 if ((bufpos - (char *)buf) + sizeof(float) <= len)
652 *(float *)bufpos = strtof(s, &s);
653 bufpos += sizeof(float);
658 while (*s == ' ' || *s == '\t')
660 if (*s++ != '"') //error, expected string
664 while (snext[0] && !(snext[-1] != '\\' && snext[0] == '"'))
668 bufpos += PADDING(bufpos, std::string *);
670 str = new std::string(s);
672 while ((pos = str->find("\\\"", pos)) != std::string::npos)
675 if ((bufpos - (char *)buf) + sizeof(std::string *) <= len)
676 *(std::string **)bufpos = str;
677 bufpos += sizeof(std::string *);
678 strs_alloced.push_back(str);
680 s = *snext ? snext + 1 : NULL;
683 while (*s == ' ' || *s == '\t')
685 if (*s++ != '(') //error, expected vector
689 bufpos += PADDING(bufpos, v2f);
691 if ((bufpos - (char *)buf) + sizeof(v2f) <= len) {
692 v2f *v = (v2f *)bufpos;
693 v->X = strtof(s, &s);
695 v->Y = strtof(s, &s);
698 bufpos += sizeof(v2f);
699 } else if (width == 3) {
700 bufpos += PADDING(bufpos, v3f);
701 if ((bufpos - (char *)buf) + sizeof(v3f) <= len) {
702 v3f *v = (v3f *)bufpos;
703 v->X = strtof(s, &s);
705 v->Y = strtof(s, &s);
707 v->Z = strtof(s, &s);
710 bufpos += sizeof(v3f);
714 default: //error, invalid format specifier
721 if ((size_t)(bufpos - (char *)buf) > len) //error, buffer too small
725 if (f && *f) { //error, mismatched number of fields and values
727 for (unsigned int i = 0; i != strs_alloced.size(); i++)
728 delete strs_alloced[i];
737 bool setStruct(std::string name, std::string format, void *value)
740 int sbuflen = sizeof(sbuf) - 1;
747 char *bufpos = (char *)value;
748 char *fmtpos, *fmt = &format[0];
749 while ((f = strtok_r(fmt, ",", &fmtpos))) {
751 bool is_unsigned = false;
752 int width = 0, nprinted = 0;
755 width = (int)strtol(f + 1, &f, 10);
756 if (width && valtype == 's')
765 bufpos += PADDING(bufpos, u16);
766 nprinted = snprintf(sbuf + pos, sbuflen,
767 is_unsigned ? "%u, " : "%d, ",
769 bufpos += sizeof(u16);
770 } else if (width == 32) {
771 bufpos += PADDING(bufpos, u32);
772 nprinted = snprintf(sbuf + pos, sbuflen,
773 is_unsigned ? "%u, " : "%d, ",
775 bufpos += sizeof(u32);
776 } else if (width == 64) {
777 bufpos += PADDING(bufpos, u64);
778 nprinted = snprintf(sbuf + pos, sbuflen,
779 is_unsigned ? "%llu, " : "%lli, ",
780 (unsigned long long)*((u64 *)bufpos));
781 bufpos += sizeof(u64);
785 bufpos += PADDING(bufpos, bool);
786 nprinted = snprintf(sbuf + pos, sbuflen, "%s, ",
787 *((bool *)bufpos) ? "true" : "false");
788 bufpos += sizeof(bool);
791 bufpos += PADDING(bufpos, float);
792 nprinted = snprintf(sbuf + pos, sbuflen, "%f, ",
794 bufpos += sizeof(float);
797 bufpos += PADDING(bufpos, std::string *);
798 str = **((std::string **)bufpos);
801 while ((fpos = str.find('"', fpos)) != std::string::npos) {
802 str.insert(fpos, 1, '\\');
806 nprinted = snprintf(sbuf + pos, sbuflen, "\"%s\", ",
807 (*((std::string **)bufpos))->c_str());
808 bufpos += sizeof(std::string *);
812 bufpos += PADDING(bufpos, v2f);
813 v2f *v = (v2f *)bufpos;
814 nprinted = snprintf(sbuf + pos, sbuflen,
815 "(%f, %f), ", v->X, v->Y);
816 bufpos += sizeof(v2f);
818 bufpos += PADDING(bufpos, v3f);
819 v3f *v = (v3f *)bufpos;
820 nprinted = snprintf(sbuf + pos, sbuflen,
821 "(%f, %f, %f), ", v->X, v->Y, v->Z);
822 bufpos += sizeof(v3f);
828 if (nprinted < 0) //error, buffer too small
837 set(name, std::string(sbuf));
841 void setFlagStr(std::string name, u32 flags, FlagDesc *flagdesc)
843 set(name, writeFlagString(flags, flagdesc));
846 void setBool(std::string name, bool value)
854 void setFloat(std::string name, float value)
856 set(name, ftos(value));
859 void setV3F(std::string name, v3f value)
861 std::ostringstream os;
862 os<<"("<<value.X<<","<<value.Y<<","<<value.Z<<")";
866 void setV2F(std::string name, v2f value)
868 std::ostringstream os;
869 os<<"("<<value.X<<","<<value.Y<<")";
873 void setS16(std::string name, s16 value)
875 set(name, itos(value));
878 void setS32(std::string name, s32 value)
880 set(name, itos(value));
883 void setU64(std::string name, u64 value)
885 std::ostringstream os;
892 JMutexAutoLock lock(m_mutex);
898 void updateValue(Settings &other, const std::string &name)
900 JMutexAutoLock lock(m_mutex);
906 std::string val = other.get(name);
907 m_settings[name] = val;
908 } catch(SettingNotFoundException &e){
914 void update(Settings &other)
916 JMutexAutoLock lock(m_mutex);
917 JMutexAutoLock lock2(other.m_mutex);
922 for(core::map<std::string, std::string>::Iterator
923 i = other.m_settings.getIterator();
924 i.atEnd() == false; i++)
926 m_settings[i.getNode()->getKey()] = i.getNode()->getValue();
929 for(core::map<std::string, std::string>::Iterator
930 i = other.m_defaults.getIterator();
931 i.atEnd() == false; i++)
933 m_defaults[i.getNode()->getKey()] = i.getNode()->getValue();
939 Settings & operator+=(Settings &other)
941 JMutexAutoLock lock(m_mutex);
942 JMutexAutoLock lock2(other.m_mutex);
947 for(core::map<std::string, std::string>::Iterator
948 i = other.m_settings.getIterator();
949 i.atEnd() == false; i++)
951 m_settings.insert(i.getNode()->getKey(),
952 i.getNode()->getValue());
955 for(core::map<std::string, std::string>::Iterator
956 i = other.m_defaults.getIterator();
957 i.atEnd() == false; i++)
959 m_defaults.insert(i.getNode()->getKey(),
960 i.getNode()->getValue());
967 Settings & operator=(Settings &other)
969 JMutexAutoLock lock(m_mutex);
970 JMutexAutoLock lock2(other.m_mutex);
982 core::map<std::string, std::string> m_settings;
983 core::map<std::string, std::string> m_defaults;
984 // All methods that access m_settings/m_defaults directly should lock this.