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"
43 VALUETYPE_FLAG // Doesn't take any arguments
48 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::ofstream os(filename);
312 if(os.good() == false)
314 errorstream<<"Error opening configuration file"
316 <<filename<<"\""<<std::endl;
323 for(std::list<std::string>::iterator
325 i != objects.end(); ++i)
331 Write stuff that was not already in the file
333 for(std::map<std::string, std::string>::iterator
334 i = m_settings.begin();
335 i != m_settings.end(); ++i)
337 if(updated.find(i->first) != updated.end())
339 std::string name = i->first;
340 std::string value = i->second;
341 infostream<<"Adding \""<<name<<"\" = \""<<value<<"\""
343 os<<name<<" = "<<value<<"\n";
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 bool getBool(std::string name)
472 return is_yes(get(name));
475 bool getFlag(std::string name)
479 return getBool(name);
481 catch(SettingNotFoundException &e)
488 bool getBoolAsk(std::string name, std::string question, bool def)
490 // If it is in settings
492 return getBool(name);
496 std::cout<<question<<" [y/N]: ";
497 std::cin.getline(templine, 10);
506 float getFloat(std::string name)
508 return stof(get(name));
511 u16 getU16(std::string name)
513 return stoi(get(name), 0, 65535);
516 u16 getU16Ask(std::string name, std::string question, u16 def)
518 // If it is in settings
524 std::cout<<question<<" ["<<def<<"]: ";
525 std::cin.getline(templine, 10);
531 return stoi(s, 0, 65535);
534 s16 getS16(std::string name)
536 return stoi(get(name), -32768, 32767);
539 s32 getS32(std::string name)
541 return stoi(get(name));
544 v3f getV3F(std::string name)
549 value.X = stof(f.next(","));
550 value.Y = stof(f.next(","));
551 value.Z = stof(f.next(")"));
555 v2f getV2F(std::string name)
560 value.X = stof(f.next(","));
561 value.Y = stof(f.next(")"));
565 u64 getU64(std::string name)
568 std::string s = get(name);
569 std::istringstream ss(s);
574 u32 getFlagStr(std::string name, FlagDesc *flagdesc)
576 std::string val = get(name);
577 return (isdigit(val[0])) ? stoi(val) : readFlagString(val, flagdesc);
580 template <class T> T *getStruct(std::string name, std::string format)
582 size_t len = sizeof(T);
583 std::vector<std::string *> strs_alloced;
585 std::string valstr = get(name);
586 char *s = &valstr[0];
588 char *bufpos = (char *)buf;
592 char *fmtpos, *fmt = &format[0];
593 while ((f = strtok_r(fmt, ",", &fmtpos)) && s) {
596 bool is_unsigned = false;
600 width = (int)strtol(f + 1, &f, 10);
601 if (width && valtype == 's')
610 bufpos += PADDING(bufpos, u16);
611 if ((bufpos - (char *)buf) + sizeof(u16) <= len) {
613 *(u16 *)bufpos = (u16)strtoul(s, &s, 10);
615 *(s16 *)bufpos = (s16)strtol(s, &s, 10);
617 bufpos += sizeof(u16);
618 } else if (width == 32) {
619 bufpos += PADDING(bufpos, u32);
620 if ((bufpos - (char *)buf) + sizeof(u32) <= len) {
622 *(u32 *)bufpos = (u32)strtoul(s, &s, 10);
624 *(s32 *)bufpos = (s32)strtol(s, &s, 10);
626 bufpos += sizeof(u32);
627 } else if (width == 64) {
628 bufpos += PADDING(bufpos, u64);
629 if ((bufpos - (char *)buf) + sizeof(u64) <= len) {
631 *(u64 *)bufpos = (u64)strtoull(s, &s, 10);
633 *(s64 *)bufpos = (s64)strtoll(s, &s, 10);
635 bufpos += sizeof(u64);
640 snext = strchr(s, ',');
644 bufpos += PADDING(bufpos, bool);
645 if ((bufpos - (char *)buf) + sizeof(bool) <= len)
646 *(bool *)bufpos = is_yes(std::string(s));
647 bufpos += sizeof(bool);
652 bufpos += PADDING(bufpos, float);
653 if ((bufpos - (char *)buf) + sizeof(float) <= len)
654 *(float *)bufpos = strtof(s, &s);
655 bufpos += sizeof(float);
660 while (*s == ' ' || *s == '\t')
662 if (*s++ != '"') //error, expected string
666 while (snext[0] && !(snext[-1] != '\\' && snext[0] == '"'))
670 bufpos += PADDING(bufpos, std::string *);
672 str = new std::string(s);
674 while ((pos = str->find("\\\"", pos)) != std::string::npos)
677 if ((bufpos - (char *)buf) + sizeof(std::string *) <= len)
678 *(std::string **)bufpos = str;
679 bufpos += sizeof(std::string *);
680 strs_alloced.push_back(str);
682 s = *snext ? snext + 1 : NULL;
685 while (*s == ' ' || *s == '\t')
687 if (*s++ != '(') //error, expected vector
691 bufpos += PADDING(bufpos, v2f);
693 if ((bufpos - (char *)buf) + sizeof(v2f) <= len) {
694 v2f *v = (v2f *)bufpos;
695 v->X = strtof(s, &s);
697 v->Y = strtof(s, &s);
700 bufpos += sizeof(v2f);
701 } else if (width == 3) {
702 bufpos += PADDING(bufpos, v3f);
703 if ((bufpos - (char *)buf) + sizeof(v3f) <= len) {
704 v3f *v = (v3f *)bufpos;
705 v->X = strtof(s, &s);
707 v->Y = strtof(s, &s);
709 v->Z = strtof(s, &s);
712 bufpos += sizeof(v3f);
716 default: //error, invalid format specifier
723 if ((size_t)(bufpos - (char *)buf) > len) //error, buffer too small
727 if (f && *f) { //error, mismatched number of fields and values
729 for (unsigned int i = 0; i != strs_alloced.size(); i++)
730 delete strs_alloced[i];
739 bool setStruct(std::string name, std::string format, void *value)
742 int sbuflen = sizeof(sbuf) - 1;
749 char *bufpos = (char *)value;
750 char *fmtpos, *fmt = &format[0];
751 while ((f = strtok_r(fmt, ",", &fmtpos))) {
753 bool is_unsigned = false;
754 int width = 0, nprinted = 0;
757 width = (int)strtol(f + 1, &f, 10);
758 if (width && valtype == 's')
767 bufpos += PADDING(bufpos, u16);
768 nprinted = snprintf(sbuf + pos, sbuflen,
769 is_unsigned ? "%u, " : "%d, ",
771 bufpos += sizeof(u16);
772 } else if (width == 32) {
773 bufpos += PADDING(bufpos, u32);
774 nprinted = snprintf(sbuf + pos, sbuflen,
775 is_unsigned ? "%u, " : "%d, ",
777 bufpos += sizeof(u32);
778 } else if (width == 64) {
779 bufpos += PADDING(bufpos, u64);
780 nprinted = snprintf(sbuf + pos, sbuflen,
781 is_unsigned ? "%llu, " : "%lli, ",
782 (unsigned long long)*((u64 *)bufpos));
783 bufpos += sizeof(u64);
787 bufpos += PADDING(bufpos, bool);
788 nprinted = snprintf(sbuf + pos, sbuflen, "%s, ",
789 *((bool *)bufpos) ? "true" : "false");
790 bufpos += sizeof(bool);
793 bufpos += PADDING(bufpos, float);
794 nprinted = snprintf(sbuf + pos, sbuflen, "%f, ",
796 bufpos += sizeof(float);
799 bufpos += PADDING(bufpos, std::string *);
800 str = **((std::string **)bufpos);
803 while ((fpos = str.find('"', fpos)) != std::string::npos) {
804 str.insert(fpos, 1, '\\');
808 nprinted = snprintf(sbuf + pos, sbuflen, "\"%s\", ",
809 (*((std::string **)bufpos))->c_str());
810 bufpos += sizeof(std::string *);
814 bufpos += PADDING(bufpos, v2f);
815 v2f *v = (v2f *)bufpos;
816 nprinted = snprintf(sbuf + pos, sbuflen,
817 "(%f, %f), ", v->X, v->Y);
818 bufpos += sizeof(v2f);
820 bufpos += PADDING(bufpos, v3f);
821 v3f *v = (v3f *)bufpos;
822 nprinted = snprintf(sbuf + pos, sbuflen,
823 "(%f, %f, %f), ", v->X, v->Y, v->Z);
824 bufpos += sizeof(v3f);
830 if (nprinted < 0) //error, buffer too small
839 set(name, std::string(sbuf));
843 void setFlagStr(std::string name, u32 flags, FlagDesc *flagdesc)
845 set(name, writeFlagString(flags, flagdesc));
848 void setBool(std::string name, bool value)
856 void setFloat(std::string name, float value)
858 set(name, ftos(value));
861 void setV3F(std::string name, v3f value)
863 std::ostringstream os;
864 os<<"("<<value.X<<","<<value.Y<<","<<value.Z<<")";
868 void setV2F(std::string name, v2f value)
870 std::ostringstream os;
871 os<<"("<<value.X<<","<<value.Y<<")";
875 void setS16(std::string name, s16 value)
877 set(name, itos(value));
880 void setS32(std::string name, s32 value)
882 set(name, itos(value));
885 void setU64(std::string name, u64 value)
887 std::ostringstream os;
894 JMutexAutoLock lock(m_mutex);
900 void updateValue(Settings &other, const std::string &name)
902 JMutexAutoLock lock(m_mutex);
908 std::string val = other.get(name);
909 m_settings[name] = val;
910 } catch(SettingNotFoundException &e){
916 void update(Settings &other)
918 JMutexAutoLock lock(m_mutex);
919 JMutexAutoLock lock2(other.m_mutex);
924 m_settings.insert(other.m_settings.begin(), other.m_settings.end());
925 m_defaults.insert(other.m_defaults.begin(), other.m_defaults.end());
930 Settings & operator+=(Settings &other)
932 JMutexAutoLock lock(m_mutex);
933 JMutexAutoLock lock2(other.m_mutex);
944 Settings & operator=(Settings &other)
946 JMutexAutoLock lock(m_mutex);
947 JMutexAutoLock lock2(other.m_mutex);
959 std::map<std::string, std::string> m_settings;
960 std::map<std::string, std::string> m_defaults;
961 // All methods that access m_settings/m_defaults directly should lock this.