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)
577 std::string val = get(name);
578 return (isdigit(val[0])) ? stoi(val) : readFlagString(val, flagdesc);
581 bool getStruct(std::string name, std::string format, void *out, size_t olen)
584 std::vector<std::string *> strs_alloced;
585 std::string *str, valstr;
591 } catch (SettingNotFoundException &e) {
595 char *s = &valstr[0];
596 char *buf = new char[len];
599 char *fmtpos, *fmt = &format[0];
600 while ((f = strtok_r(fmt, ",", &fmtpos)) && s) {
603 bool is_unsigned = false;
607 width = (int)strtol(f + 1, &f, 10);
608 if (width && valtype == 's')
617 bufpos += PADDING(bufpos, u16);
618 if ((bufpos - buf) + sizeof(u16) <= len) {
620 *(u16 *)bufpos = (u16)strtoul(s, &s, 10);
622 *(s16 *)bufpos = (s16)strtol(s, &s, 10);
624 bufpos += sizeof(u16);
625 } else if (width == 32) {
626 bufpos += PADDING(bufpos, u32);
627 if ((bufpos - buf) + sizeof(u32) <= len) {
629 *(u32 *)bufpos = (u32)strtoul(s, &s, 10);
631 *(s32 *)bufpos = (s32)strtol(s, &s, 10);
633 bufpos += sizeof(u32);
634 } else if (width == 64) {
635 bufpos += PADDING(bufpos, u64);
636 if ((bufpos - buf) + sizeof(u64) <= len) {
638 *(u64 *)bufpos = (u64)strtoull(s, &s, 10);
640 *(s64 *)bufpos = (s64)strtoll(s, &s, 10);
642 bufpos += sizeof(u64);
647 snext = strchr(s, ',');
651 bufpos += PADDING(bufpos, bool);
652 if ((bufpos - buf) + sizeof(bool) <= len)
653 *(bool *)bufpos = is_yes(std::string(s));
654 bufpos += sizeof(bool);
659 bufpos += PADDING(bufpos, float);
660 if ((bufpos - buf) + sizeof(float) <= len)
661 *(float *)bufpos = strtof(s, &s);
662 bufpos += sizeof(float);
667 while (*s == ' ' || *s == '\t')
669 if (*s++ != '"') //error, expected string
673 while (snext[0] && !(snext[-1] != '\\' && snext[0] == '"'))
677 bufpos += PADDING(bufpos, std::string *);
679 str = new std::string(s);
681 while ((pos = str->find("\\\"", pos)) != std::string::npos)
684 if ((bufpos - buf) + sizeof(std::string *) <= len)
685 *(std::string **)bufpos = str;
686 bufpos += sizeof(std::string *);
687 strs_alloced.push_back(str);
689 s = *snext ? snext + 1 : NULL;
692 while (*s == ' ' || *s == '\t')
694 if (*s++ != '(') //error, expected vector
698 bufpos += PADDING(bufpos, v2f);
700 if ((bufpos - buf) + sizeof(v2f) <= len) {
701 v2f *v = (v2f *)bufpos;
702 v->X = strtof(s, &s);
704 v->Y = strtof(s, &s);
707 bufpos += sizeof(v2f);
708 } else if (width == 3) {
709 bufpos += PADDING(bufpos, v3f);
710 if ((bufpos - buf) + sizeof(v3f) <= len) {
711 v3f *v = (v3f *)bufpos;
712 v->X = strtof(s, &s);
714 v->Y = strtof(s, &s);
716 v->Z = strtof(s, &s);
719 bufpos += sizeof(v3f);
723 default: //error, invalid format specifier
730 if ((size_t)(bufpos - buf) > len) //error, buffer too small
734 if (f && *f) { //error, mismatched number of fields and values
736 for (size_t i = 0; i != strs_alloced.size(); i++)
737 delete strs_alloced[i];
742 memcpy(out, buf, olen);
747 //////////// Try to get value, no exception thrown
748 bool getNoEx(std::string name, std::string &val)
753 } catch (SettingNotFoundException &e) {
755 } catch (NumericException &e) {
760 bool getFlagStrNoEx(std::string name, u32 &val, FlagDesc *flagdesc)
763 val = getFlagStr(name, flagdesc);
765 } catch (SettingNotFoundException &e) {
770 bool getFloatNoEx(std::string name, float &val)
773 val = getFloat(name);
775 } catch (SettingNotFoundException &e) {
777 } catch (NumericException &e) {
782 bool getU16NoEx(std::string name, int &val)
787 } catch (SettingNotFoundException &e) {
789 } catch (NumericException &e) {
794 bool getU16NoEx(std::string name, u16 &val)
799 } catch (SettingNotFoundException &e) {
801 } catch (NumericException &e) {
806 bool getS16NoEx(std::string name, int &val)
811 } catch (SettingNotFoundException &e) {
813 } catch (NumericException &e) {
818 bool getS16NoEx(std::string name, s16 &val)
823 } catch (SettingNotFoundException &e) {
825 } catch (NumericException &e) {
830 bool getS32NoEx(std::string name, s32 &val)
835 } catch (SettingNotFoundException &e) {
837 } catch (NumericException &e) {
842 bool getV3FNoEx(std::string name, v3f &val)
847 } catch (SettingNotFoundException &e) {
849 } catch (NumericException &e) {
854 bool getV2FNoEx(std::string name, v2f &val)
859 } catch (SettingNotFoundException &e) {
861 } catch (NumericException &e) {
866 bool getU64NoEx(std::string name, u64 &val)
871 } catch (SettingNotFoundException &e) {
873 } catch (NumericException &e) {
878 //////////// Set setting
879 bool setStruct(std::string name, std::string format, void *value)
882 int sbuflen = sizeof(sbuf) - 1;
889 char *bufpos = (char *)value;
890 char *fmtpos, *fmt = &format[0];
891 while ((f = strtok_r(fmt, ",", &fmtpos))) {
893 bool is_unsigned = false;
894 int width = 0, nprinted = 0;
897 width = (int)strtol(f + 1, &f, 10);
898 if (width && valtype == 's')
907 bufpos += PADDING(bufpos, u16);
908 nprinted = snprintf(sbuf + pos, sbuflen,
909 is_unsigned ? "%u, " : "%d, ",
911 bufpos += sizeof(u16);
912 } else if (width == 32) {
913 bufpos += PADDING(bufpos, u32);
914 nprinted = snprintf(sbuf + pos, sbuflen,
915 is_unsigned ? "%u, " : "%d, ",
917 bufpos += sizeof(u32);
918 } else if (width == 64) {
919 bufpos += PADDING(bufpos, u64);
920 nprinted = snprintf(sbuf + pos, sbuflen,
921 is_unsigned ? "%llu, " : "%lli, ",
922 (unsigned long long)*((u64 *)bufpos));
923 bufpos += sizeof(u64);
927 bufpos += PADDING(bufpos, bool);
928 nprinted = snprintf(sbuf + pos, sbuflen, "%s, ",
929 *((bool *)bufpos) ? "true" : "false");
930 bufpos += sizeof(bool);
933 bufpos += PADDING(bufpos, float);
934 nprinted = snprintf(sbuf + pos, sbuflen, "%f, ",
936 bufpos += sizeof(float);
939 bufpos += PADDING(bufpos, std::string *);
940 str = **((std::string **)bufpos);
943 while ((fpos = str.find('"', fpos)) != std::string::npos) {
944 str.insert(fpos, 1, '\\');
948 nprinted = snprintf(sbuf + pos, sbuflen, "\"%s\", ",
949 (*((std::string **)bufpos))->c_str());
950 bufpos += sizeof(std::string *);
954 bufpos += PADDING(bufpos, v2f);
955 v2f *v = (v2f *)bufpos;
956 nprinted = snprintf(sbuf + pos, sbuflen,
957 "(%f, %f), ", v->X, v->Y);
958 bufpos += sizeof(v2f);
960 bufpos += PADDING(bufpos, v3f);
961 v3f *v = (v3f *)bufpos;
962 nprinted = snprintf(sbuf + pos, sbuflen,
963 "(%f, %f, %f), ", v->X, v->Y, v->Z);
964 bufpos += sizeof(v3f);
970 if (nprinted < 0) //error, buffer too small
979 set(name, std::string(sbuf));
983 void setFlagStr(std::string name, u32 flags, FlagDesc *flagdesc)
985 set(name, writeFlagString(flags, flagdesc));
988 void setBool(std::string name, bool value)
996 void setFloat(std::string name, float value)
998 set(name, ftos(value));
1001 void setV3F(std::string name, v3f value)
1003 std::ostringstream os;
1004 os<<"("<<value.X<<","<<value.Y<<","<<value.Z<<")";
1005 set(name, os.str());
1008 void setV2F(std::string name, v2f value)
1010 std::ostringstream os;
1011 os<<"("<<value.X<<","<<value.Y<<")";
1012 set(name, os.str());
1015 void setS16(std::string name, s16 value)
1017 set(name, itos(value));
1020 void setS32(std::string name, s32 value)
1022 set(name, itos(value));
1025 void setU64(std::string name, u64 value)
1027 std::ostringstream os;
1029 set(name, os.str());
1034 JMutexAutoLock lock(m_mutex);
1040 void updateValue(Settings &other, const std::string &name)
1042 JMutexAutoLock lock(m_mutex);
1048 std::string val = other.get(name);
1049 m_settings[name] = val;
1050 } catch(SettingNotFoundException &e){
1056 void update(Settings &other)
1058 JMutexAutoLock lock(m_mutex);
1059 JMutexAutoLock lock2(other.m_mutex);
1064 m_settings.insert(other.m_settings.begin(), other.m_settings.end());
1065 m_defaults.insert(other.m_defaults.begin(), other.m_defaults.end());
1070 Settings & operator+=(Settings &other)
1072 JMutexAutoLock lock(m_mutex);
1073 JMutexAutoLock lock2(other.m_mutex);
1084 Settings & operator=(Settings &other)
1086 JMutexAutoLock lock(m_mutex);
1087 JMutexAutoLock lock2(other.m_mutex);
1099 std::map<std::string, std::string> m_settings;
1100 std::map<std::string, std::string> m_defaults;
1101 // All methods that access m_settings/m_defaults directly should lock this.