Properly and efficiently use split utility headers
[oweals/minetest.git] / src / settings.h
1 /*
2 Minetest-c55
3 Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
4
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.
9
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.
14
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.
18 */
19
20 #ifndef SETTINGS_HEADER
21 #define SETTINGS_HEADER
22
23 #include "irrlichttypes.h"
24 #include <string>
25 #include <jthread.h>
26 #include <jmutex.h>
27 #include <jmutexautolock.h>
28 #include "strfnd.h"
29 #include <iostream>
30 #include <fstream>
31 #include <sstream>
32 #include "debug.h"
33 #include "log.h"
34 #include "util/string.h"
35
36 enum ValueType
37 {
38         VALUETYPE_STRING,
39         VALUETYPE_FLAG // Doesn't take any arguments
40 };
41
42 struct ValueSpec
43 {
44         ValueSpec(ValueType a_type, const char *a_help=NULL)
45         {
46                 type = a_type;
47                 help = a_help;
48         }
49         ValueType type;
50         const char *help;
51 };
52
53 class Settings
54 {
55 public:
56         Settings()
57         {
58                 m_mutex.Init();
59         }
60
61         void writeLines(std::ostream &os)
62         {
63                 JMutexAutoLock lock(m_mutex);
64                 
65                 for(core::map<std::string, std::string>::Iterator
66                                 i = m_settings.getIterator();
67                                 i.atEnd() == false; i++)
68                 {
69                         std::string name = i.getNode()->getKey();
70                         std::string value = i.getNode()->getValue();
71                         os<<name<<" = "<<value<<"\n";
72                 }
73         }
74
75         bool parseConfigLine(const std::string &line)
76         {
77                 JMutexAutoLock lock(m_mutex);
78                 
79                 std::string trimmedline = trim(line);
80                 
81                 // Ignore empty lines and comments
82                 if(trimmedline.size() == 0 || trimmedline[0] == '#')
83                         return true;
84
85                 //infostream<<"trimmedline=\""<<trimmedline<<"\""<<std::endl;
86
87                 Strfnd sf(trim(line));
88
89                 std::string name = sf.next("=");
90                 name = trim(name);
91
92                 if(name == "")
93                         return true;
94                 
95                 std::string value = sf.next("\n");
96                 value = trim(value);
97
98                 /*infostream<<"Config name=\""<<name<<"\" value=\""
99                                 <<value<<"\""<<std::endl;*/
100                 
101                 m_settings[name] = value;
102                 
103                 return true;
104         }
105
106         void parseConfigLines(std::istream &is, const std::string &endstring)
107         {
108                 for(;;){
109                         if(is.eof())
110                                 break;
111                         std::string line;
112                         std::getline(is, line);
113                         std::string trimmedline = trim(line);
114                         if(endstring != ""){
115                                 if(trimmedline == endstring)
116                                         break;
117                         }
118                         parseConfigLine(line);
119                 }
120         }
121
122         // Returns false on EOF
123         bool parseConfigObject(std::istream &is)
124         {
125                 if(is.eof())
126                         return false;
127                 
128                 /*
129                         NOTE: This function might be expanded to allow multi-line
130                               settings.
131                 */
132                 std::string line;
133                 std::getline(is, line);
134                 //infostream<<"got line: \""<<line<<"\""<<std::endl;
135
136                 return parseConfigLine(line);
137         }
138
139         /*
140                 Read configuration file
141
142                 Returns true on success
143         */
144         bool readConfigFile(const char *filename)
145         {
146                 std::ifstream is(filename);
147                 if(is.good() == false)
148                         return false;
149
150                 /*infostream<<"Parsing configuration file: \""
151                                 <<filename<<"\""<<std::endl;*/
152                                 
153                 while(parseConfigObject(is));
154                 
155                 return true;
156         }
157
158         /*
159                 Reads a configuration object from stream (usually a single line)
160                 and adds it to dst.
161                 
162                 Preserves comments and empty lines.
163
164                 Settings that were added to dst are also added to updated.
165                 key of updated is setting name, value of updated is dummy.
166
167                 Returns false on EOF
168         */
169         bool getUpdatedConfigObject(std::istream &is,
170                         core::list<std::string> &dst,
171                         core::map<std::string, bool> &updated,
172                         bool &value_changed)
173         {
174                 JMutexAutoLock lock(m_mutex);
175                 
176                 if(is.eof())
177                         return false;
178                 
179                 // NOTE: This function will be expanded to allow multi-line settings
180                 std::string line;
181                 std::getline(is, line);
182
183                 std::string trimmedline = trim(line);
184
185                 std::string line_end = "";
186                 if(is.eof() == false)
187                         line_end = "\n";
188                 
189                 // Ignore empty lines and comments
190                 if(trimmedline.size() == 0 || trimmedline[0] == '#')
191                 {
192                         dst.push_back(line+line_end);
193                         return true;
194                 }
195
196                 Strfnd sf(trim(line));
197
198                 std::string name = sf.next("=");
199                 name = trim(name);
200
201                 if(name == "")
202                 {
203                         dst.push_back(line+line_end);
204                         return true;
205                 }
206                 
207                 std::string value = sf.next("\n");
208                 value = trim(value);
209                 
210                 if(m_settings.find(name))
211                 {
212                         std::string newvalue = m_settings[name];
213                         
214                         if(newvalue != value)
215                         {
216                                 infostream<<"Changing value of \""<<name<<"\" = \""
217                                                 <<value<<"\" -> \""<<newvalue<<"\""
218                                                 <<std::endl;
219                                 value_changed = true;
220                         }
221
222                         dst.push_back(name + " = " + newvalue + line_end);
223
224                         updated[name] = true;
225                 }
226                 
227                 return true;
228         }
229
230         /*
231                 Updates configuration file
232
233                 Returns true on success
234         */
235         bool updateConfigFile(const char *filename)
236         {
237                 infostream<<"Updating configuration file: \""
238                                 <<filename<<"\""<<std::endl;
239                 
240                 core::list<std::string> objects;
241                 core::map<std::string, bool> updated;
242                 bool something_actually_changed = false;
243                 
244                 // Read and modify stuff
245                 {
246                         std::ifstream is(filename);
247                         if(is.good() == false)
248                         {
249                                 infostream<<"updateConfigFile():"
250                                                 " Error opening configuration file"
251                                                 " for reading: \""
252                                                 <<filename<<"\""<<std::endl;
253                         }
254                         else
255                         {
256                                 while(getUpdatedConfigObject(is, objects, updated,
257                                                 something_actually_changed));
258                         }
259                 }
260                 
261                 JMutexAutoLock lock(m_mutex);
262                 
263                 // If something not yet determined to have been changed, check if
264                 // any new stuff was added
265                 if(!something_actually_changed){
266                         for(core::map<std::string, std::string>::Iterator
267                                         i = m_settings.getIterator();
268                                         i.atEnd() == false; i++)
269                         {
270                                 if(updated.find(i.getNode()->getKey()))
271                                         continue;
272                                 something_actually_changed = true;
273                                 break;
274                         }
275                 }
276                 
277                 // If nothing was actually changed, skip writing the file
278                 if(!something_actually_changed){
279                         infostream<<"Skipping writing of "<<filename
280                                         <<" because content wouldn't be modified"<<std::endl;
281                         return true;
282                 }
283                 
284                 // Write stuff back
285                 {
286                         std::ofstream os(filename);
287                         if(os.good() == false)
288                         {
289                                 errorstream<<"Error opening configuration file"
290                                                 " for writing: \""
291                                                 <<filename<<"\""<<std::endl;
292                                 return false;
293                         }
294                         
295                         /*
296                                 Write updated stuff
297                         */
298                         for(core::list<std::string>::Iterator
299                                         i = objects.begin();
300                                         i != objects.end(); i++)
301                         {
302                                 os<<(*i);
303                         }
304
305                         /*
306                                 Write stuff that was not already in the file
307                         */
308                         for(core::map<std::string, std::string>::Iterator
309                                         i = m_settings.getIterator();
310                                         i.atEnd() == false; i++)
311                         {
312                                 if(updated.find(i.getNode()->getKey()))
313                                         continue;
314                                 std::string name = i.getNode()->getKey();
315                                 std::string value = i.getNode()->getValue();
316                                 infostream<<"Adding \""<<name<<"\" = \""<<value<<"\""
317                                                 <<std::endl;
318                                 os<<name<<" = "<<value<<"\n";
319                         }
320                 }
321                 
322                 return true;
323         }
324
325         /*
326                 NOTE: Types of allowed_options are ignored
327
328                 returns true on success
329         */
330         bool parseCommandLine(int argc, char *argv[],
331                         core::map<std::string, ValueSpec> &allowed_options)
332         {
333                 int nonopt_index = 0;
334                 int i=1;
335                 for(;;)
336                 {
337                         if(i >= argc)
338                                 break;
339                         std::string argname = argv[i];
340                         if(argname.substr(0, 2) != "--")
341                         {
342                                 // If option doesn't start with -, read it in as nonoptX
343                                 if(argname[0] != '-'){
344                                         std::string name = "nonopt";
345                                         name += itos(nonopt_index);
346                                         set(name, argname);
347                                         nonopt_index++;
348                                         i++;
349                                         continue;
350                                 }
351                                 errorstream<<"Invalid command-line parameter \""
352                                                 <<argname<<"\": --<option> expected."<<std::endl;
353                                 return false;
354                         }
355                         i++;
356
357                         std::string name = argname.substr(2);
358
359                         core::map<std::string, ValueSpec>::Node *n;
360                         n = allowed_options.find(name);
361                         if(n == NULL)
362                         {
363                                 errorstream<<"Unknown command-line parameter \""
364                                                 <<argname<<"\""<<std::endl;
365                                 return false;
366                         }
367
368                         ValueType type = n->getValue().type;
369
370                         std::string value = "";
371                         
372                         if(type == VALUETYPE_FLAG)
373                         {
374                                 value = "true";
375                         }
376                         else
377                         {
378                                 if(i >= argc)
379                                 {
380                                         errorstream<<"Invalid command-line parameter \""
381                                                         <<name<<"\": missing value"<<std::endl;
382                                         return false;
383                                 }
384                                 value = argv[i];
385                                 i++;
386                         }
387                         
388
389                         infostream<<"Valid command-line parameter: \""
390                                         <<name<<"\" = \""<<value<<"\""
391                                         <<std::endl;
392                         set(name, value);
393                 }
394
395                 return true;
396         }
397
398         void set(std::string name, std::string value)
399         {
400                 JMutexAutoLock lock(m_mutex);
401                 
402                 m_settings[name] = value;
403         }
404
405         void set(std::string name, const char *value)
406         {
407                 JMutexAutoLock lock(m_mutex);
408
409                 m_settings[name] = value;
410         }
411
412
413         void setDefault(std::string name, std::string value)
414         {
415                 JMutexAutoLock lock(m_mutex);
416                 
417                 m_defaults[name] = value;
418         }
419
420         bool exists(std::string name)
421         {
422                 JMutexAutoLock lock(m_mutex);
423                 
424                 return (m_settings.find(name) || m_defaults.find(name));
425         }
426
427         std::string get(std::string name)
428         {
429                 JMutexAutoLock lock(m_mutex);
430                 
431                 core::map<std::string, std::string>::Node *n;
432                 n = m_settings.find(name);
433                 if(n == NULL)
434                 {
435                         n = m_defaults.find(name);
436                         if(n == NULL)
437                         {
438                                 throw SettingNotFoundException("Setting not found");
439                         }
440                 }
441
442                 return n->getValue();
443         }
444
445         bool getBool(std::string name)
446         {
447                 return is_yes(get(name));
448         }
449         
450         bool getFlag(std::string name)
451         {
452                 try
453                 {
454                         return getBool(name);
455                 }
456                 catch(SettingNotFoundException &e)
457                 {
458                         return false;
459                 }
460         }
461
462         // Asks if empty
463         bool getBoolAsk(std::string name, std::string question, bool def)
464         {
465                 // If it is in settings
466                 if(exists(name))
467                         return getBool(name);
468                 
469                 std::string s;
470                 char templine[10];
471                 std::cout<<question<<" [y/N]: ";
472                 std::cin.getline(templine, 10);
473                 s = templine;
474
475                 if(s == "")
476                         return def;
477
478                 return is_yes(s);
479         }
480
481         float getFloat(std::string name)
482         {
483                 return stof(get(name));
484         }
485
486         u16 getU16(std::string name)
487         {
488                 return stoi(get(name), 0, 65535);
489         }
490
491         u16 getU16Ask(std::string name, std::string question, u16 def)
492         {
493                 // If it is in settings
494                 if(exists(name))
495                         return getU16(name);
496                 
497                 std::string s;
498                 char templine[10];
499                 std::cout<<question<<" ["<<def<<"]: ";
500                 std::cin.getline(templine, 10);
501                 s = templine;
502
503                 if(s == "")
504                         return def;
505
506                 return stoi(s, 0, 65535);
507         }
508
509         s16 getS16(std::string name)
510         {
511                 return stoi(get(name), -32768, 32767);
512         }
513
514         s32 getS32(std::string name)
515         {
516                 return stoi(get(name));
517         }
518
519         v3f getV3F(std::string name)
520         {
521                 v3f value;
522                 Strfnd f(get(name));
523                 f.next("(");
524                 value.X = stof(f.next(","));
525                 value.Y = stof(f.next(","));
526                 value.Z = stof(f.next(")"));
527                 return value;
528         }
529
530         v2f getV2F(std::string name)
531         {
532                 v2f value;
533                 Strfnd f(get(name));
534                 f.next("(");
535                 value.X = stof(f.next(","));
536                 value.Y = stof(f.next(")"));
537                 return value;
538         }
539
540         u64 getU64(std::string name)
541         {
542                 u64 value = 0;
543                 std::string s = get(name);
544                 std::istringstream ss(s);
545                 ss>>value;
546                 return value;
547         }
548
549         void setBool(std::string name, bool value)
550         {
551                 if(value)
552                         set(name, "true");
553                 else
554                         set(name, "false");
555         }
556
557         void setS32(std::string name, s32 value)
558         {
559                 set(name, itos(value));
560         }
561
562         void setFloat(std::string name, float value)
563         {
564                 set(name, ftos(value));
565         }
566
567         void setV3F(std::string name, v3f value)
568         {
569                 std::ostringstream os;
570                 os<<"("<<value.X<<","<<value.Y<<","<<value.Z<<")";
571                 set(name, os.str());
572         }
573
574         void setV2F(std::string name, v2f value)
575         {
576                 std::ostringstream os;
577                 os<<"("<<value.X<<","<<value.Y<<")";
578                 set(name, os.str());
579         }
580
581         void setU64(std::string name, u64 value)
582         {
583                 std::ostringstream os;
584                 os<<value;
585                 set(name, os.str());
586         }
587
588         void clear()
589         {
590                 JMutexAutoLock lock(m_mutex);
591                 
592                 m_settings.clear();
593                 m_defaults.clear();
594         }
595
596         void updateValue(Settings &other, const std::string &name)
597         {
598                 JMutexAutoLock lock(m_mutex);
599                 
600                 if(&other == this)
601                         return;
602
603                 try{
604                         std::string val = other.get(name);
605                         m_settings[name] = val;
606                 } catch(SettingNotFoundException &e){
607                 }
608
609                 return;
610         }
611
612         void update(Settings &other)
613         {
614                 JMutexAutoLock lock(m_mutex);
615                 JMutexAutoLock lock2(other.m_mutex);
616                 
617                 if(&other == this)
618                         return;
619
620                 for(core::map<std::string, std::string>::Iterator
621                                 i = other.m_settings.getIterator();
622                                 i.atEnd() == false; i++)
623                 {
624                         m_settings[i.getNode()->getKey()] = i.getNode()->getValue();
625                 }
626                 
627                 for(core::map<std::string, std::string>::Iterator
628                                 i = other.m_defaults.getIterator();
629                                 i.atEnd() == false; i++)
630                 {
631                         m_defaults[i.getNode()->getKey()] = i.getNode()->getValue();
632                 }
633
634                 return;
635         }
636
637         Settings & operator+=(Settings &other)
638         {
639                 JMutexAutoLock lock(m_mutex);
640                 JMutexAutoLock lock2(other.m_mutex);
641                 
642                 if(&other == this)
643                         return *this;
644
645                 for(core::map<std::string, std::string>::Iterator
646                                 i = other.m_settings.getIterator();
647                                 i.atEnd() == false; i++)
648                 {
649                         m_settings.insert(i.getNode()->getKey(),
650                                         i.getNode()->getValue());
651                 }
652                 
653                 for(core::map<std::string, std::string>::Iterator
654                                 i = other.m_defaults.getIterator();
655                                 i.atEnd() == false; i++)
656                 {
657                         m_defaults.insert(i.getNode()->getKey(),
658                                         i.getNode()->getValue());
659                 }
660
661                 return *this;
662
663         }
664
665         Settings & operator=(Settings &other)
666         {
667                 JMutexAutoLock lock(m_mutex);
668                 JMutexAutoLock lock2(other.m_mutex);
669                 
670                 if(&other == this)
671                         return *this;
672
673                 clear();
674                 (*this) += other;
675                 
676                 return *this;
677         }
678
679 private:
680         core::map<std::string, std::string> m_settings;
681         core::map<std::string, std::string> m_defaults;
682         // All methods that access m_settings/m_defaults directly should lock this.
683         JMutex m_mutex;
684 };
685
686 #endif
687