1 /*
2 	Copyright (C) 2004-2006 Christopher E. Miller
3 	http://www.dprogramming.com/ini.php
4 
5 	This software is provided 'as-is', without any express or implied
6 	warranty.  In no event will the authors be held liable for any damages
7 	arising from the use of this software.
8 
9 	Permission is granted to anyone to use this software for any purpose,
10 	including commercial applications, and to alter it and redistribute it
11 	freely, subject to the following restrictions:
12 
13 	1. The origin of this software must not be misrepresented; you must not
14 	   claim that you wrote the original software. If you use this software
15 	   in a product, an acknowledgment in the product documentation would be
16 	   appreciated but is not required.
17 	2. Altered source versions must be plainly marked as such, and must not be
18 	   misrepresented as being the original software.
19 	3. This notice may not be removed or altered from any source distribution.
20 */
21 
22 /*
23 
24 	Modified by Jesse Phillips
25 	Made to work with D 2.0.
26 	Changed all string to string.
27 	Added some @safe and nothrow
28 	Other changes marked
29 
30 Update:
31 The Ini object no longer saves in the destructor because if it is the
32 garbage collector deleting it, some value or section object could have
33 been destructed first, resulting in undefined behavior, such as an
34 access violation. Solution: save() before you exit the program.
35 
36 
37 Portable module for reading and writing INI files of the format:
38 
39 [section]
40 key=value
41 ...
42 
43 Leading spaces and tabs are ignored.
44 Comments start with ; and should be on their own line.
45 
46 If there are comments, spaces or keys above the first section, a nameless section is created for them.
47 This means there need not be any sections in the file to have keys.
48 
49 Differences with Windows' profile (INI) functions:
50 * Windows 9x does not allow tabs in the value.
51 * Some versions do not allow the file to exceed 64 KB.
52 * If not a full file path, it's relative to the Windows directory.
53 * Windows 9x strips trailing spaces from the value.
54 * There might be a restriction on how long sections/keys/values may be.
55 * If there are double quotes around a value, Windows removes them.
56 * All key/value pairs must be in a named section.
57 
58 Проведена модификация MGW 18.02.2016
59 Отключена работа с устаревшим модулем std.stream
60 
61 */
62 
63 
64 /// Portable module for reading and writing _INI files. _ini.d version 0.6
65 module ini;
66 
67 import std.stdio; // writeln
68 
69 private import std.file, std..string; 
70 
71 
72 // debug = INI; //show file being parsed
73 
74 
75 // debug(INI)
76 // private import std.cstream;
77 
78 private class IniLine {
79 	~this() {
80 		debug(PRINT_DTORS) writeln("~IniLine\n");
81 	}
82 
83 private:
84 	string data;
85 }
86 
87 /// Key in an INI file.
88 class IniKey: IniLine {
89 protected:
90 	//these are slices in data if unmodified
91 	//if modified, data is set to null
92 	string _name;
93 	string _value;
94 
95 public:
96 	this(string name) {
97 		_name = name;
98 	}
99 
100 
101 	~this() {
102 		debug(PRINT_DTORS) writeln("~IniKey ", _name);
103 	}
104 
105 
106 // public: +++ GENA
107 	/// Property: get key _name.
108 	@property string name() { //-> имя
109 		return _name;
110 	}
111 
112 
113 	/// Property: get key _value.
114 	@property string value() { //-> значение
115 		return _value;
116 	}
117 }
118 
119 
120 /// Section of keys in an INI file.
121 class IniSection {
122 protected:
123 	Ini _ini;				// Ссылка на объект Ini
124 	string _name;			// Имя секции
125 	IniLine[] lines;		// Массив строк в секции
126 
127 	// Создание. ссылка на объект Ini и имя секции
128 	this(Ini ini, string name) {
129 		_ini = ini;			// Ссылка на объект Ini
130 		_name = name;		// Имя секции
131 	}
132 
133 	~this() {
134 		debug(PRINT_DTORS) writeln("~IniSection ", _name);
135 	}
136 
137 
138 public:
139 	/// Property: get section _name. Вернуть имя секции
140 	@property @safe nothrow
141 	string name() { //-> Вернуть/установить имя секции
142 		return _name; 
143 	}
144 
145 
146 	/// Property: set section _name. Установить имя секции. Взвести флаг модификации
147 	@property @safe nothrow
148 	void name(string newName) { //-> Вернуть/установить имя секции
149 		_ini._modified = true;
150 		_name = newName;
151 	}
152 
153 
154 	/// foreach key. Перебор ключей в цикле
155 	int opApply(int delegate(ref IniKey) dg) { //-> Найти нужный ключ
156 		int result = 0;
157 		uint i;
158 		IniKey ikey;
159 		for(i = 0; i != lines.length; i++) {
160 			ikey = cast(IniKey)lines[i];
161 			if(ikey) {
162 				result = dg(ikey);
163 				if(result)
164 					break;
165 			}
166 		}
167 		return result;
168 	}
169 
170 
171 	/// Property: get all _keys. Дай перечень всех ключей
172 	//better to use foreach unless this array is needed
173 	@property IniKey[] keys() { //-> Дай перечень всех ключей
174 		IniKey[] ikeys = new IniKey[lines.length];
175 		uint i = 0;
176 		foreach(IniKey ikey; this) {
177 			ikeys[i++] = ikey;
178 		}
179 		return ikeys[0 .. i];
180 	}
181 
182 
183 	/// Returns: _key matching keyName, or null if not present. Вернуть ключ или null если отсутствует
184 	IniKey key(string keyName) { //-> Вернуть ключ или null если отсутствует
185 		foreach(IniKey ikey; this) {
186 			if(_ini.match(ikey._name, keyName))
187 				return ikey;
188 		}
189 		return new IniKey(keyName); //didn't find it
190 	}
191 
192 
193 	/// Set an existing key's value.
194 	@safe nothrow
195 	void setValue(IniKey ikey, string newValue) { //-> Установить значение если существует
196 		ikey._value = newValue;
197 		_ini._modified = true;
198 		ikey.data = null;
199 	}
200 
201 
202 	/// Find or create key keyName and set its _value to newValue.
203 	void setValue(string keyName, string newValue) { //-> Найти или создать и установить значение
204 		IniKey ikey = key(keyName);
205 		if(!ikey.data) { // MGW Add ref on .data in ikey
206 			ikey = new IniKey(keyName);
207 			lines ~= ikey;  // К списку lines добавить ещё одну строку
208 			// _ini._modified = true; //next call does this
209 		}
210 		value(ikey, newValue);
211 	}
212 
213 
214 	/+
215 	///
216 	alias setValue value;
217 	+/
218 
219 
220 	/// Same as setValue(ikey, newValue).
221 	@safe nothrow
222 	void value(IniKey ikey, string newValue) {
223 		return setValue(ikey, newValue);
224 	}
225 
226 
227 	/// Same as setValue(keyName, newValue).
228 	void value(string keyName, string newValue) {
229 		// asm {
230 		//     int 3;
231 		// }
232 		return setValue(keyName, newValue);
233 	}
234 
235 
236 	/// Returns: value of the existing key keyName, or defaultValue if not present.
237 	string getValue(string keyName, string defaultValue = null) {
238 		foreach(IniKey ikey; this) {
239 			if(_ini.match(ikey._name, keyName))
240 				return ikey.value;
241 		}
242 		return defaultValue; //didn't find it
243 	}
244 
245 
246 	// /// Returns: _value of the existing key keyName, or null if not present.
247 	/// Same as getValue(keyName, null).
248 	string value(string keyName) { //->
249 		return getValue(keyName, null);
250 	}
251 
252 
253 	/// Shortcut for getValue(keyName).
254 	string opIndex(string keyName) { //-> Доступ по имени
255 		return value(keyName);
256 	}
257 
258 
259 	/// Shortcut for setValue(keyName, newValue).
260 	void opIndexAssign(string newValue, string keyName) { //->
261 		value(keyName, newValue);
262 	}
263 
264 
265 	/// _Remove key keyName.
266 	void remove(string keyName) { //-> Удалить ключ
267 		uint i;
268 		IniKey ikey;
269 		for(i = 0; i != lines.length; i++) {
270 			ikey = cast(IniKey)lines[i];
271 			if(ikey && _ini.match(ikey._name, keyName)) {
272 				if(i == lines.length - 1)
273 					lines = lines[0 .. i];
274 				else if(i == 0)
275 					lines = lines[1 .. lines.length];
276 				else
277 					lines = lines[0 .. i] ~ lines[i + 1 .. lines.length];
278 				_ini._modified = true;
279 				return;
280 			}
281 		}
282 	}
283 }
284 
285 
286 /// An INI file.
287 class Ini {
288 // protected:
289 	string _file;								// Имя исходного INI файла
290 	bool _modified = false;						// F - файл не изменялся
291 	IniSection[] isecs;
292 	char secStart = '[', secEnd = ']';
293 
294 	void print() {
295 		writeln("================ Class INI ===============");
296 		writeln("_file = ", _file);
297 		writeln("_modified = ", _modified);
298 		for(int i; i<isecs.length; i++) {
299 			writeln("    ", isecs[i]._name);
300 			for(int j; j<isecs[i].lines.length; j++) {
301 				IniKey ikey = cast(IniKey)isecs[i].lines[j];
302 				writeln("        ", isecs[i].lines[j].data);
303 				try {
304 					writeln("               [", ikey._name, "] --> [", ikey._value, "]  (", ikey.data,")");
305 				} catch(Throwable) {
306 				}
307 			}
308 		}
309 		writeln("==========================================");
310 	}
311 
312 	void parse() {
313 		debug(INI) writeln("INI parsing file ", _file);
314 		string data;
315 		int i = -1;
316 		IniSection isec;
317 		uint lineStartIndex = 0;
318 
319 		try {
320 			data = cast(string)std.file.read(_file);
321 			if(data.length>2 && data[0]==239 && data[1]==187 && data[2]==191) data = data[3 .. $].dup;
322 			
323 			/+
324 			File f = new File(_file, FileMode.In);
325 			data = f.readString(f.size());
326 			delete f;
327 			+/
328 		}
329 		// catch(Throwable o)
330 		catch(Throwable) {
331 			debug(INI)
332 			writeln("INI no file to parse");
333 			// File f = new File(_file, "w");
334 			// write(f," ");
335 			// f.close();
336 
337 			return;
338 		}
339 		if(!data.length) {
340 			debug(INI)
341 			writeln("INI nothing to parse");
342 			return;
343 		}
344 
345 
346 		char getc() {
347 			//also increment -i- past end so ungetc works properly
348 			if(++i >= data.length)
349 				return 0;
350 			return data[i];
351 		}
352 
353 
354 		void ungetc() {
355 			assert(i > 0);
356 			i--;
357 		}
358 
359 
360 		void reset() {
361 			lineStartIndex = i + 1;
362 		}
363 
364 
365 		void eol() {
366 			IniLine iline = new IniLine;
367 			iline.data = data[lineStartIndex .. i];
368 			debug(INI) writeln("INI line: ", iline.data);
369 			isec.lines ~= iline;
370 		}
371 
372 
373 		char ch, ch2;
374 		int i2;
375 		isec = new IniSection(this, "");
376 		for(;;) {
377 			ch = getc();
378 			switch(ch) {
379 			case '\r':
380 				eol();
381 				ch2 = getc();
382 				if(ch2 != '\n')
383 					ungetc();
384 				reset();
385 				break;
386 
387 			case '\n':
388 				eol();
389 				reset();
390 				break;
391 
392 			case 0: //eof
393 ini_eof:
394 				if(lineStartIndex < i) {
395 					eol();
396 					//reset();
397 				}
398 				isecs ~= isec;
399 				if(!isecs[0].lines)
400 					isecs = isecs[1 .. isecs.length];
401 				debug(INI)
402 				writeln("INI done parsing");
403 				return;
404 
405 			case ' ':
406 			case '\t':
407 			case '\v':
408 			case '\f':
409 				break;
410 
411 			case ';': //comments
412 			case '#':
413 done_comment:
414 				for(;;) {
415 					ch2 = getc();
416 					switch(ch2) {
417 					case '\r':
418 						eol();
419 						ch2 = getc();
420 						if(ch2 != '\n')
421 							ungetc();
422 						reset();
423 						break done_comment;
424 
425 					case '\n':
426 						eol();
427 						reset();
428 						break done_comment;
429 
430 					case 0: //eof
431 						goto ini_eof;
432 
433 					default:
434 						break;
435 					}
436 				}
437 				break;
438 
439 			default:
440 				if(ch == secStart) { // '['
441 					i2 = i + 1;
442 done_sec:
443 					for(;;) {
444 						ch2 = getc();
445 						switch(ch2) {
446 						case '\r':
447 							eol();
448 							ch2 = getc();
449 							if(ch2 != '\n')
450 								ungetc();
451 							reset();
452 							break done_sec;
453 
454 						case '\n':
455 							eol();
456 							reset();
457 							break done_sec;
458 
459 						case 0: //eof
460 							goto ini_eof;
461 
462 						default:
463 							if(ch2 == secEnd) { // ']'
464 								isecs ~= isec;
465 								isec = new IniSection(this, data[i2 .. i]);
466 								debug(INI) writeln("INI section: ", isec._name);
467 								for(;;) {
468 									ch2 = getc();
469 									switch(ch2) {
470 									case ' ':
471 									case '\t':
472 									case '\v':
473 									case '\f':
474 										//ignore whitespace
475 										break;
476 
477 									case '\r':
478 										ch2 = getc();
479 										if(ch2 != '\n')
480 											ungetc();
481 										break done_sec;
482 
483 									case '\n':
484 										break done_sec;
485 
486 									default:
487 										//just treat junk after the ] as the next line
488 										ungetc();
489 										break done_sec;
490 									}
491 								}
492 								break done_sec;
493 							}
494 						}
495 					}
496 					reset();
497 					break;
498 				} else { //must be beginning of key name
499 					i2 = i;
500 done_default:
501 					for(;;) {
502 						ch2 = getc();
503 						switch(ch2) {
504 						case '\r':
505 							eol();
506 							ch2 = getc();
507 							if(ch2 != '\n')
508 								ungetc();
509 							reset();
510 							break done_default;
511 
512 						case '\n':
513 							eol();
514 							reset();
515 							break done_default;
516 
517 						case 0: //eof
518 							goto ini_eof;
519 
520 						case ' ':
521 						case '\t':
522 						case '\v':
523 						case '\f':
524 							break;
525 
526 						case '=':
527 							IniKey ikey;
528 
529 
530 							void addKey() {
531 								ikey.data = data[lineStartIndex .. i];
532 								ikey._value = data[i2 .. i];
533 								isec.lines ~= ikey;
534 								debug(INI) writeln("INI key: [", ikey._name, "] = [", ikey._value, "]");
535 							}
536 
537 
538 							ikey = new IniKey(data[i2 .. i]);
539 							i2 = i + 1; //after =
540 							for(;;) { //get key value
541 								ch2 = getc();
542 								switch(ch2) {
543 								case '\r':
544 									addKey();
545 									ch2 = getc();
546 									if(ch2 != '\n')
547 										ungetc();
548 									reset();
549 									break done_default;
550 
551 								case '\n':
552 									addKey();
553 									reset();
554 									break done_default;
555 
556 								case 0: //eof
557 									addKey();
558 									reset();
559 									goto ini_eof;
560 
561 								default:
562 									break;
563 								}
564 							}
565 							break done_default;
566 
567 						default:
568 							break;
569 						}
570 					}
571 				}
572 			}
573 		}
574 	}
575 
576 
577 	void firstOpen(string file) {
578 		//null terminated just to make it easier for the implementation
579 		//_file = toStringz(file)[0 .. file.length];
580 		// JP Modified
581 		_file = file;
582 		parse();
583 	}
584 
585 
586 public:
587 	// Added by Jesse Phillips
588 	/// Upon the next save use this file.
589 	string saveTo;
590 	// Use different section name delimiters; not recommended.
591 	this(string file, char secStart, char secEnd) {
592 		this.secStart = secStart;
593 		this.secEnd = secEnd;
594 
595 		firstOpen(file);
596 	}
597 
598 
599 	/// Construct a new INI _file.
600 	this(string file) {
601 		firstOpen(file);
602 	}
603 
604 
605 	~this() {
606 		debug(PRINT_DTORS)
607 		writeln("~Ini ", _file);
608 
609 		// The reason this is commented is explained above.
610 		/+
611 		if(_modified)
612 			save();
613 		+/
614 	}
615 
616 
617 	/// Comparison function for section and key names. Override to change behavior.
618 	bool match(string s1, string s2) {
619 		return !std..string.icmp(s1, s2);
620 	}
621 
622 
623 	//reuse same object for another file
624 	/// Open an INI _file.
625 	void open(string file) {
626 		if(_modified)
627 			save();
628 		_modified = false;
629 		isecs = null;
630 
631 		firstOpen(file);
632 	}
633 
634 
635 	/// Reload INI file; any unsaved changes are lost.
636 	void rehash() {
637 		_modified = false;
638 		isecs = null;
639 		parse();
640 	}
641 
642 
643 	/// Release memory without saving changes; contents become empty.
644 	@safe nothrow
645 	void dump() {
646 		_modified = false;
647 		isecs = null;
648 	}
649 
650 
651 	/// Property: get whether or not the INI file was _modified since it was loaded or saved.
652 	@property @safe nothrow
653 	bool modified() {
654 		return _modified;
655 	}
656 
657 
658 	/// Params:
659 	/// f = an opened-for-write stream; save() uses BufferedFile by default. Override save() to change stream.
660 	protected final void saveToStream(File f) {
661 		_modified = false;
662 
663 		// Если массив секций пустой, то выйти
664 		if(!isecs.length) return;
665 
666 		IniKey ikey;
667 		IniSection isec;
668 		uint i = 0, j;
669 
670 		if(isecs[0]._name.length)
671 			goto write_name;
672 		else //first section doesn't have a name; just keys at start of file
673 			goto after_name;
674 
675 		for(; i != isecs.length; i++) {
676 write_name:
677 			// JP Modified added dup
678 			f.writeln(secStart, isecs[i]._name, secEnd);
679 after_name:
680 			isec = isecs[i];
681 			for(j = 0; j != isec.lines.length; j++) {
682 				if(isec.lines[j].data is null) {
683 					ikey = cast(IniKey)isec.lines[j];
684 					if(ikey)
685 						ikey.data = ikey._name ~ "=" ~ ikey._value;
686 				}
687 				f.writeln(isec.lines[j].data);
688 			}
689 		}
690 	}
691 
692 	/// Write contents to disk, even if no changes were made. It is common to do if(modified)save();
693 	void save() {
694 		if(saveTo) {
695 			_file = saveTo;
696 			saveTo = null;
697 		}
698 		File f = File(_file, "w");
699 		// f.create(_file);
700 		try {
701 			saveToStream(f);
702 			f.flush();
703 		}
704 		finally {
705 			f.close();
706 		}
707 	}
708 
709 	/// Write contents to disk with filename
710 	// Added by Jesse Phillips
711 	void save(string filename) {
712 		_file = filename;
713 		save();
714 	}
715 
716 
717 	/// Finds a _section; returns null if one named name does not exist.
718 	IniSection section(string name) {
719 		foreach(IniSection isec; isecs) {
720 			if(match(isec._name, name))
721 				return isec;
722 		}
723 		return null; //didn't find it
724 	}
725 
726 
727 	/// Shortcut for section(sectionName).
728 	IniSection opIndex(string sectionName) {
729 		return section(sectionName);
730 	}
731 
732 
733 	/// The section is created if one named name does not exist.
734 	/// Returns: Section named name.
735 	IniSection addSection(string name) {
736 		IniSection isec = section(name);
737 		if(!isec) {
738 			isec = new IniSection(this, name);
739 			_modified = true;
740 			isecs ~= isec;
741 		}
742 		return isec;
743 	}
744 
745 
746 	/// foreach section.
747 	int opApply(int delegate(ref IniSection) dg) {
748 		int result = 0;
749 		foreach(IniSection isec; isecs) {
750 			result = dg(isec);
751 			if(result)
752 				break;
753 		}
754 		return result;
755 	}
756 
757 
758 	/// Property: get all _sections.
759 	@property @safe nothrow
760 	IniSection[] sections() {
761 		return isecs;
762 	}
763 
764 
765 	/// _Remove section named sectionName.
766 	void remove(string sectionName) {
767 		uint i;
768 		for(i = 0; i != isecs.length; i++) {
769 			if(match(sectionName, isecs[i]._name)) {
770 				if(i == isecs.length - 1)
771 					isecs = isecs[0 .. i];
772 				else if(i == 0)
773 					isecs = isecs[1 .. isecs.length];
774 				else
775 					isecs = isecs[0 .. i] ~ isecs[i + 1 .. isecs.length];
776 				_modified = true;
777 				return;
778 			}
779 		}
780 	}
781 }
782 
783 
784 unittest {
785 	string inifile = "unittest.ini";
786 	// Jesse Phillips
787 	// Remove file when done.
788 	scope(exit)
789 	std.file.remove(inifile);
790 	Ini ini;
791 
792 	ini = new Ini(inifile);
793 	with(ini.addSection("foo")) {
794 		value("asdf", "jkl");
795 		value("bar", "wee!");
796 		value("hi", "hello");
797 	}
798 	ini.addSection("BAR");
799 	with(ini.addSection("fOO")) {
800 		value("yes", "no");
801 	}
802 	with(ini.addSection("Hello")) {
803 		value("world", "true");
804 	}
805 	with(ini.addSection("test")) {
806 		value("1", "2");
807 		value("3", "4");
808 	}
809 	ini["test"]["value"] = "true";
810 	assert(ini["Foo"]["yes"] == "no");
811 	ini.save();
812 	delete ini;
813 
814 	ini = new Ini(inifile);
815 	assert(ini["FOO"]["Bar"] == "wee!"); //
816 	assert(ini["Foo"]["yes"] == "no");
817 	assert(ini["hello"]["world"] == "true");
818 	assert(ini["FOO"]["Bar"] == "wee!");
819 	assert(ini["55"] is null);
820 	assert(ini["hello"]["Yes"] is null);
821 
822 	ini.open(inifile);
823 	ini["bar"].remove("notta");
824 	ini["foo"].remove("bar");
825 	ini.remove("bar");
826 	assert(ini["bar"] is null);
827 	assert(ini["foo"] !is null);
828 	assert(ini["foo"]["bar"] is null);
829 	ini.remove("foo");
830 	assert(ini["foo"] is null);
831 	ini.save();
832 	delete ini;
833 }