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"
44 VALUETYPE_FLAG // Doesn't take any arguments
49 ValueSpec(ValueType a_type, const char *a_help=NULL)
66 void writeLines(std::ostream &os)
68 JMutexAutoLock lock(m_mutex);
70 for(std::map<std::string, std::string>::iterator
71 i = m_settings.begin();
72 i != m_settings.end(); ++i)
74 std::string name = i->first;
75 std::string value = i->second;
76 os<<name<<" = "<<value<<"\n";
80 // return all keys used
81 std::vector<std::string> getNames(){
82 std::vector<std::string> names;
83 for(std::map<std::string, std::string>::iterator
84 i = m_settings.begin();
85 i != m_settings.end(); ++i)
87 names.push_back(i->first);
93 bool remove(const std::string& name)
95 return m_settings.erase(name);
99 bool parseConfigLine(const std::string &line)
101 JMutexAutoLock lock(m_mutex);
103 std::string trimmedline = trim(line);
105 // Ignore empty lines and comments
106 if(trimmedline.size() == 0 || trimmedline[0] == '#')
109 //infostream<<"trimmedline=\""<<trimmedline<<"\""<<std::endl;
111 Strfnd sf(trim(line));
113 std::string name = sf.next("=");
119 std::string value = sf.next("\n");
122 /*infostream<<"Config name=\""<<name<<"\" value=\""
123 <<value<<"\""<<std::endl;*/
125 m_settings[name] = value;
130 void parseConfigLines(std::istream &is, const std::string &endstring)
136 std::getline(is, line);
137 std::string trimmedline = trim(line);
139 if(trimmedline == endstring)
142 parseConfigLine(line);
146 // Returns false on EOF
147 bool parseConfigObject(std::istream &is)
153 NOTE: This function might be expanded to allow multi-line
157 std::getline(is, line);
158 //infostream<<"got line: \""<<line<<"\""<<std::endl;
160 return parseConfigLine(line);
164 Read configuration file
166 Returns true on success
168 bool readConfigFile(const char *filename)
170 std::ifstream is(filename);
171 if(is.good() == false)
174 /*infostream<<"Parsing configuration file: \""
175 <<filename<<"\""<<std::endl;*/
177 while(parseConfigObject(is));
183 Reads a configuration object from stream (usually a single line)
186 Preserves comments and empty lines.
188 Settings that were added to dst are also added to updated.
189 key of updated is setting name, value of updated is dummy.
193 bool getUpdatedConfigObject(std::istream &is,
194 std::list<std::string> &dst,
195 std::set<std::string> &updated,
198 JMutexAutoLock lock(m_mutex);
203 // NOTE: This function will be expanded to allow multi-line settings
205 std::getline(is, line);
207 std::string trimmedline = trim(line);
209 std::string line_end = "";
210 if(is.eof() == false)
213 // Ignore empty lines and comments
214 if(trimmedline.size() == 0 || trimmedline[0] == '#')
216 dst.push_back(line+line_end);
220 Strfnd sf(trim(line));
222 std::string name = sf.next("=");
227 dst.push_back(line+line_end);
231 std::string value = sf.next("\n");
234 if(m_settings.find(name) != m_settings.end())
236 std::string newvalue = m_settings[name];
238 if(newvalue != value)
240 infostream<<"Changing value of \""<<name<<"\" = \""
241 <<value<<"\" -> \""<<newvalue<<"\""
243 value_changed = true;
246 dst.push_back(name + " = " + newvalue + line_end);
248 updated.insert(name);
250 else //file contains a setting which is not in m_settings
257 Updates configuration file
259 Returns true on success
261 bool updateConfigFile(const char *filename)
263 infostream<<"Updating configuration file: \""
264 <<filename<<"\""<<std::endl;
266 std::list<std::string> objects;
267 std::set<std::string> updated;
268 bool something_actually_changed = false;
270 // Read and modify stuff
272 std::ifstream is(filename);
273 if(is.good() == false)
275 infostream<<"updateConfigFile():"
276 " Error opening configuration file"
278 <<filename<<"\""<<std::endl;
282 while(getUpdatedConfigObject(is, objects, updated,
283 something_actually_changed));
287 JMutexAutoLock lock(m_mutex);
289 // If something not yet determined to have been changed, check if
290 // any new stuff was added
291 if(!something_actually_changed){
292 for(std::map<std::string, std::string>::iterator
293 i = m_settings.begin();
294 i != m_settings.end(); ++i)
296 if(updated.find(i->first) != updated.end())
298 something_actually_changed = true;
303 // If nothing was actually changed, skip writing the file
304 if(!something_actually_changed){
305 infostream<<"Skipping writing of "<<filename
306 <<" because content wouldn't be modified"<<std::endl;
312 std::ostringstream ss(std::ios_base::binary);
317 for(std::list<std::string>::iterator
319 i != objects.end(); ++i)
325 Write stuff that was not already in the file
327 for(std::map<std::string, std::string>::iterator
328 i = m_settings.begin();
329 i != m_settings.end(); ++i)
331 if(updated.find(i->first) != updated.end())
333 std::string name = i->first;
334 std::string value = i->second;
335 infostream<<"Adding \""<<name<<"\" = \""<<value<<"\""
337 ss<<name<<" = "<<value<<"\n";
340 if(!fs::safeWriteToFile(filename, ss.str()))
342 errorstream<<"Error writing configuration file: \""
343 <<filename<<"\""<<std::endl;
352 NOTE: Types of allowed_options are ignored
354 returns true on success
356 bool parseCommandLine(int argc, char *argv[],
357 std::map<std::string, ValueSpec> &allowed_options)
359 int nonopt_index = 0;
365 std::string argname = argv[i];
366 if(argname.substr(0, 2) != "--")
368 // If option doesn't start with -, read it in as nonoptX
369 if(argname[0] != '-'){
370 std::string name = "nonopt";
371 name += itos(nonopt_index);
377 errorstream<<"Invalid command-line parameter \""
378 <<argname<<"\": --<option> expected."<<std::endl;
383 std::string name = argname.substr(2);
385 std::map<std::string, ValueSpec>::iterator n;
386 n = allowed_options.find(name);
387 if(n == allowed_options.end())
389 errorstream<<"Unknown command-line parameter \""
390 <<argname<<"\""<<std::endl;
394 ValueType type = n->second.type;
396 std::string value = "";
398 if(type == VALUETYPE_FLAG)
406 errorstream<<"Invalid command-line parameter \""
407 <<name<<"\": missing value"<<std::endl;
415 infostream<<"Valid command-line parameter: \""
416 <<name<<"\" = \""<<value<<"\""
424 void set(std::string name, std::string value)
426 JMutexAutoLock lock(m_mutex);
428 m_settings[name] = value;
431 void set(std::string name, const char *value)
433 JMutexAutoLock lock(m_mutex);
435 m_settings[name] = value;
439 void setDefault(std::string name, std::string value)
441 JMutexAutoLock lock(m_mutex);
443 m_defaults[name] = value;
446 bool exists(std::string name)
448 JMutexAutoLock lock(m_mutex);
450 return (m_settings.find(name) != m_settings.end() || m_defaults.find(name) != m_defaults.end());
453 std::string get(std::string name)
455 JMutexAutoLock lock(m_mutex);
457 std::map<std::string, std::string>::iterator n;
458 n = m_settings.find(name);
459 if(n == m_settings.end())
461 n = m_defaults.find(name);
462 if(n == m_defaults.end())
464 throw SettingNotFoundException(("Setting [" + name + "] not found ").c_str());
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;
586 std::string valstr = get(name);
587 char *s = &valstr[0];
588 char *buf = new char[len];
593 char *fmtpos, *fmt = &format[0];
594 while ((f = strtok_r(fmt, ",", &fmtpos)) && s) {
597 bool is_unsigned = false;
601 width = (int)strtol(f + 1, &f, 10);
602 if (width && valtype == 's')
611 bufpos += PADDING(bufpos, u16);
612 if ((bufpos - buf) + sizeof(u16) <= len) {
614 *(u16 *)bufpos = (u16)strtoul(s, &s, 10);
616 *(s16 *)bufpos = (s16)strtol(s, &s, 10);
618 bufpos += sizeof(u16);
619 } else if (width == 32) {
620 bufpos += PADDING(bufpos, u32);
621 if ((bufpos - buf) + sizeof(u32) <= len) {
623 *(u32 *)bufpos = (u32)strtoul(s, &s, 10);
625 *(s32 *)bufpos = (s32)strtol(s, &s, 10);
627 bufpos += sizeof(u32);
628 } else if (width == 64) {
629 bufpos += PADDING(bufpos, u64);
630 if ((bufpos - buf) + sizeof(u64) <= len) {
632 *(u64 *)bufpos = (u64)strtoull(s, &s, 10);
634 *(s64 *)bufpos = (s64)strtoll(s, &s, 10);
636 bufpos += sizeof(u64);
641 snext = strchr(s, ',');
645 bufpos += PADDING(bufpos, bool);
646 if ((bufpos - buf) + sizeof(bool) <= len)
647 *(bool *)bufpos = is_yes(std::string(s));
648 bufpos += sizeof(bool);
653 bufpos += PADDING(bufpos, float);
654 if ((bufpos - buf) + sizeof(float) <= len)
655 *(float *)bufpos = strtof(s, &s);
656 bufpos += sizeof(float);
661 while (*s == ' ' || *s == '\t')
663 if (*s++ != '"') //error, expected string
667 while (snext[0] && !(snext[-1] != '\\' && snext[0] == '"'))
671 bufpos += PADDING(bufpos, std::string *);
673 str = new std::string(s);
675 while ((pos = str->find("\\\"", pos)) != std::string::npos)
678 if ((bufpos - buf) + sizeof(std::string *) <= len)
679 *(std::string **)bufpos = str;
680 bufpos += sizeof(std::string *);
681 strs_alloced.push_back(str);
683 s = *snext ? snext + 1 : NULL;
686 while (*s == ' ' || *s == '\t')
688 if (*s++ != '(') //error, expected vector
692 bufpos += PADDING(bufpos, v2f);
694 if ((bufpos - buf) + sizeof(v2f) <= len) {
695 v2f *v = (v2f *)bufpos;
696 v->X = strtof(s, &s);
698 v->Y = strtof(s, &s);
701 bufpos += sizeof(v2f);
702 } else if (width == 3) {
703 bufpos += PADDING(bufpos, v3f);
704 if ((bufpos - buf) + sizeof(v3f) <= len) {
705 v3f *v = (v3f *)bufpos;
706 v->X = strtof(s, &s);
708 v->Y = strtof(s, &s);
710 v->Z = strtof(s, &s);
713 bufpos += sizeof(v3f);
717 default: //error, invalid format specifier
724 if ((size_t)(bufpos - buf) > len) //error, buffer too small
728 if (f && *f) { //error, mismatched number of fields and values
730 for (size_t i = 0; i != strs_alloced.size(); i++)
731 delete strs_alloced[i];
736 memcpy(out, buf, olen);
741 bool setStruct(std::string name, std::string format, void *value)
744 int sbuflen = sizeof(sbuf) - 1;
751 char *bufpos = (char *)value;
752 char *fmtpos, *fmt = &format[0];
753 while ((f = strtok_r(fmt, ",", &fmtpos))) {
755 bool is_unsigned = false;
756 int width = 0, nprinted = 0;
759 width = (int)strtol(f + 1, &f, 10);
760 if (width && valtype == 's')
769 bufpos += PADDING(bufpos, u16);
770 nprinted = snprintf(sbuf + pos, sbuflen,
771 is_unsigned ? "%u, " : "%d, ",
773 bufpos += sizeof(u16);
774 } else if (width == 32) {
775 bufpos += PADDING(bufpos, u32);
776 nprinted = snprintf(sbuf + pos, sbuflen,
777 is_unsigned ? "%u, " : "%d, ",
779 bufpos += sizeof(u32);
780 } else if (width == 64) {
781 bufpos += PADDING(bufpos, u64);
782 nprinted = snprintf(sbuf + pos, sbuflen,
783 is_unsigned ? "%llu, " : "%lli, ",
784 (unsigned long long)*((u64 *)bufpos));
785 bufpos += sizeof(u64);
789 bufpos += PADDING(bufpos, bool);
790 nprinted = snprintf(sbuf + pos, sbuflen, "%s, ",
791 *((bool *)bufpos) ? "true" : "false");
792 bufpos += sizeof(bool);
795 bufpos += PADDING(bufpos, float);
796 nprinted = snprintf(sbuf + pos, sbuflen, "%f, ",
798 bufpos += sizeof(float);
801 bufpos += PADDING(bufpos, std::string *);
802 str = **((std::string **)bufpos);
805 while ((fpos = str.find('"', fpos)) != std::string::npos) {
806 str.insert(fpos, 1, '\\');
810 nprinted = snprintf(sbuf + pos, sbuflen, "\"%s\", ",
811 (*((std::string **)bufpos))->c_str());
812 bufpos += sizeof(std::string *);
816 bufpos += PADDING(bufpos, v2f);
817 v2f *v = (v2f *)bufpos;
818 nprinted = snprintf(sbuf + pos, sbuflen,
819 "(%f, %f), ", v->X, v->Y);
820 bufpos += sizeof(v2f);
822 bufpos += PADDING(bufpos, v3f);
823 v3f *v = (v3f *)bufpos;
824 nprinted = snprintf(sbuf + pos, sbuflen,
825 "(%f, %f, %f), ", v->X, v->Y, v->Z);
826 bufpos += sizeof(v3f);
832 if (nprinted < 0) //error, buffer too small
841 set(name, std::string(sbuf));
845 void setFlagStr(std::string name, u32 flags, FlagDesc *flagdesc)
847 set(name, writeFlagString(flags, flagdesc));
850 void setBool(std::string name, bool value)
858 void setFloat(std::string name, float value)
860 set(name, ftos(value));
863 void setV3F(std::string name, v3f value)
865 std::ostringstream os;
866 os<<"("<<value.X<<","<<value.Y<<","<<value.Z<<")";
870 void setV2F(std::string name, v2f value)
872 std::ostringstream os;
873 os<<"("<<value.X<<","<<value.Y<<")";
877 void setS16(std::string name, s16 value)
879 set(name, itos(value));
882 void setS32(std::string name, s32 value)
884 set(name, itos(value));
887 void setU64(std::string name, u64 value)
889 std::ostringstream os;
896 JMutexAutoLock lock(m_mutex);
902 void updateValue(Settings &other, const std::string &name)
904 JMutexAutoLock lock(m_mutex);
910 std::string val = other.get(name);
911 m_settings[name] = val;
912 } catch(SettingNotFoundException &e){
918 void update(Settings &other)
920 JMutexAutoLock lock(m_mutex);
921 JMutexAutoLock lock2(other.m_mutex);
926 m_settings.insert(other.m_settings.begin(), other.m_settings.end());
927 m_defaults.insert(other.m_defaults.begin(), other.m_defaults.end());
932 Settings & operator+=(Settings &other)
934 JMutexAutoLock lock(m_mutex);
935 JMutexAutoLock lock2(other.m_mutex);
946 Settings & operator=(Settings &other)
948 JMutexAutoLock lock(m_mutex);
949 JMutexAutoLock lock2(other.m_mutex);
961 std::map<std::string, std::string> m_settings;
962 std::map<std::string, std::string> m_defaults;
963 // All methods that access m_settings/m_defaults directly should lock this.