1 /*
2  * This file is part of gir-to-d.
3  *
4  * gir-to-d is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License
6  * as published by the Free Software Foundation, either version 3
7  * of the License, or (at your option) any later version.
8  *
9  * gir-to-d is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with gir-to-d.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 module gtd.DefReader;
19 
20 import std.algorithm;
21 import std.array;
22 import std.conv : hexString;
23 import std.file;
24 import std.string : splitLines, strip, indexOf;
25 
26 import gtd.WrapException;
27 
28 public final class DefReader
29 {
30 	string fileName;
31 	string key;
32 	string subKey;
33 	string value;
34 
35 	private size_t _lineNumber;
36 	private size_t lineOffset;
37 	private string[] lines;
38 
39 	public this(string fileName)
40 	{
41 		this.fileName = fileName;
42 
43 		lines = readText(fileName).splitLines();
44 		//Skip utf8 BOM.
45 		lines[0].skipOver(hexString!"efbbbf");
46 
47 		this.popFront();
48 	}
49 
50 	/**
51 	 * Proccess the _lines defined in lines.
52 	 * The fileName and lineOffset are only used for error reporting.
53 	 */
54 	public this(string[] lines, string fileName = "", size_t lineOffset = 0)
55 	{
56 		this.lines = lines;
57 		this.fileName = fileName;
58 		this.lineOffset = lineOffset;
59 		this.popFront();
60 	}
61 
62 	public void popFront()
63 	{
64 		string line;
65 
66 		if ( !lines.empty )
67 		{
68 			line = lines.front.strip();
69 			lines.popFront();
70 			_lineNumber++;
71 
72 			while ( !lines.empty && ( line.empty || line.startsWith("#") ) )
73 			{
74 				line = lines.front.strip();
75 				lines.popFront();
76 				_lineNumber++;
77 			}
78 		}
79 
80 		if ( !line.empty && !line.startsWith("#") )
81 		{
82 			ptrdiff_t index = line.indexOf(':');
83 
84 			key    = line[0 .. max(index, 0)].strip();
85 			value  = line[index +1 .. $].strip();
86 			subKey = "";
87 
88 			index = key.indexOf(' ');
89 			if ( index != -1 )
90 			{
91 				subKey = key[index +1 .. $].strip();
92 				key    = key[0 .. index].strip();
93 			}
94 		}
95 		else
96 		{
97 			key.length = 0;
98 			value.length = 0;
99 			subKey.length = 0;
100 		}
101 	}
102 
103 	/**
104 	 * Gets the content of a block value
105 	 */
106 	public string[] readBlock(string key = "")
107 	{
108 		string[] block;
109 
110 		if ( key.empty )
111 			key = this.key;
112 
113 		while ( !lines.empty )
114 		{
115 			if ( startsWith(lines.front.strip(), key) )
116 			{
117 				lines.popFront();
118 				_lineNumber++;
119 				return block;
120 			}
121 
122 			block ~= lines.front ~ '\n';
123 			lines.popFront();
124 			_lineNumber++;
125 		}
126 
127 		throw new LookupException(this, "Found EOF while expecting: \""~key~": end\"");
128 	}
129 
130 	/**
131 	 * Skip the content of a block. Supports nested blocks.
132 	 */
133 	public void skipBlock(string key = "")
134 	{
135 		if ( key.empty )
136 			key = this.key;
137 
138 		size_t nestedBlocks = 1;
139 		do
140 		{
141 			do lines.popFront; while ( !lines.front.strip().startsWith(key) );
142 
143 			if ( lines.front.strip().endsWith("start") )
144 				nestedBlocks++;
145 			else if ( lines.front.strip().endsWith("end") )
146 				nestedBlocks--;
147 		}
148 		while ( nestedBlocks > 0 );
149 	}
150 
151 	/**
152 	 * Gets the current value as a bool
153 	 */
154 	public @property bool valueBool() const
155 	{
156 		return !!value.among("1", "ok", "OK", "Ok", "true", "TRUE", "True", "Y", "y", "yes", "YES", "Yes");
157 	}
158 
159 	public @property bool empty() const
160 	{
161 		return lines.empty && key.empty;
162 	}
163 
164 	public @property size_t lineNumber() const
165 	{
166 		return _lineNumber + lineOffset;
167 	}
168 }
169 
170 class LookupException : WrapException
171 {
172 	this(DefReader defReader, string msg)
173 	{
174 		super(msg, defReader.fileName, defReader.lineNumber);
175 	}
176 }