00001 #ifndef _PASSENGER_INI_FILE_H_
00002 #define _PASSENGER_INI_FILE_H_
00003
00004 #include <utility>
00005 #include <string>
00006 #include <map>
00007 #include <fstream>
00008 #include <iostream>
00009 #include <cstdio>
00010 #include <cstring>
00011 #include <cassert>
00012 #include <cctype>
00013 #include <boost/shared_ptr.hpp>
00014 #include <boost/make_shared.hpp>
00015 #include <Exceptions.h>
00016
00017 namespace Passenger {
00018
00019 using namespace std;
00020 using namespace boost;
00021
00022
00023 class IniFileSection {
00024 protected:
00025 typedef map<string, string> ValueMap;
00026 string sectionName;
00027 ValueMap values;
00028
00029 public:
00030 IniFileSection(const string §ionName) {
00031 this->sectionName = sectionName;
00032 }
00033
00034 bool hasKey(const string &keyName) const {
00035 return values.find(keyName) != values.end();
00036 }
00037
00038 string get(const string &keyName) const {
00039 ValueMap::const_iterator it = values.find(keyName);
00040 if (it != values.end()) {
00041 return it->second;
00042 } else {
00043 return string();
00044 }
00045 }
00046
00047 string operator[](const string &keyName) const {
00048 return get(keyName);
00049 }
00050
00051 void set(const string &keyName, const string &value) {
00052 values[keyName] = value;
00053 }
00054
00055 string getSectionName() const {
00056 return sectionName;
00057 }
00058
00059 void inspect() const {
00060 ValueMap::const_iterator it = values.begin();
00061 while (it != values.end()) {
00062 cout << it->first << " = " << it->second << endl;
00063 it++;
00064 }
00065 }
00066 };
00067
00068 class IniFileLexer {
00069 public:
00070 class Token {
00071 public:
00072 enum Kind {
00073 UNKNOWN = 0,
00074 NEWLINE,
00075 SECTION_NAME,
00076 IDENTIFIER,
00077 ASSIGNMENT,
00078 TEXT,
00079 END_OF_FILE
00080 };
00081
00082 const Kind kind;
00083 const string value;
00084 const int line;
00085 const int column;
00086
00087
00088 const static char *identityByKind(Kind kind) {
00089 const static char* KIND_IDENTITY_TABLE[] = {
00090 "<T_UNKNOWN>",
00091 "<T_NEWLINE>",
00092 "<T_SECTION_NAME>",
00093 "<T_IDENTIFIER>",
00094 "<T_ASSIGNMENT>",
00095 "<T_TEXT>",
00096 "<T_EOF>"
00097 };
00098
00099 return KIND_IDENTITY_TABLE[kind];
00100 }
00101
00102 Token(const Kind kind, const string &value, const int line, const int column)
00103 : kind(kind), value(value), line(line), column(column) {
00104
00105 }
00106
00107 class ExpectanceException : public std::exception {
00108 private:
00109 char message[255];
00110
00111 public:
00112 ExpectanceException(char expected, char got, int line, int column) {
00113 int messageSize = sizeof(message);
00114 memset(message, 0, messageSize);
00115 snprintf(message, messageSize,
00116 "On line %i, column %i: Expected '%c', got '%c' instead.",
00117 line, column, expected, got);
00118 }
00119
00120 ExpectanceException(Token::Kind expected, Token got) {
00121 const char *expectedKindString = Token::identityByKind(expected);
00122 int messageSize = sizeof(message);
00123 memset(message, 0, messageSize);
00124 snprintf(message, messageSize,
00125 "On line %i, column %i: Expected '%s', got '%s' instead.",
00126 got.line, got.column, expectedKindString, got.value.c_str());
00127 }
00128
00129 ExpectanceException(char expected, Token::Kind got, int line, int column) {
00130 const char *gotKindString = Token::identityByKind(got);
00131 int messageSize = sizeof(message);
00132 memset(message, 0, messageSize);
00133 snprintf(message, messageSize,
00134 "On line %i, column %i: Expected '%c', got '%s' instead.",
00135 line, column, expected, gotKindString);
00136 }
00137
00138 virtual const char* what() const throw() {
00139 return message;
00140 }
00141 };
00142 };
00143
00144 typedef shared_ptr<IniFileLexer::Token> TokenPtr;
00145
00146
00147
00148 protected:
00149 ifstream iniFileStream;
00150
00151 char lastAcceptedChar;
00152 char upcomingChar;
00153 bool upcomingTokenPtrIsStale;
00154
00155 int currentLine;
00156 int currentColumn;
00157
00158 TokenPtr upcomingTokenPtr;
00159
00160 void expect(char ch) {
00161 char upcomingChar = (char)iniFileStream.peek();
00162
00163 if (ch != upcomingChar) {
00164 switch(upcomingChar) {
00165 case EOF:
00166 throw Token::ExpectanceException(ch, Token::END_OF_FILE,
00167 currentLine, currentColumn + 1);
00168 case '\n':
00169 throw Token::ExpectanceException(ch, upcomingChar,
00170 currentLine + 1, 0);
00171 default:
00172 throw Token::ExpectanceException(ch, upcomingChar,
00173 currentLine, currentColumn + 1);
00174 }
00175 }
00176 }
00177
00178 void accept() {
00179 if (upcomingChar == EOF) return;
00180
00181 lastAcceptedChar = (char)iniFileStream.get();
00182 upcomingChar = (char)iniFileStream.peek();
00183 currentColumn++;
00184
00185 if (lastAcceptedChar == '\n') {
00186 currentLine++;
00187 currentColumn = 1;
00188 }
00189 }
00190
00191 void ignore() {
00192 if (upcomingChar == EOF) return;
00193
00194 upcomingChar = (char)iniFileStream.peek();
00195 currentColumn++;
00196
00197 if ((char)iniFileStream.get() == '\n') {
00198 currentLine++;
00199 currentColumn = 1;
00200 }
00201 }
00202
00203 void expectAndAccept(char ch) {
00204 expect(ch);
00205 accept();
00206 }
00207
00208 void acceptWhileNewLine() {
00209 while (iniFileStream.good() && upcomingChar == '\n') {
00210 accept();
00211 }
00212 }
00213
00214 void ignoreWhileNotNewLine() {
00215 while (iniFileStream.good() && upcomingChar != '\n') {
00216 ignore();
00217 }
00218 }
00219
00220 Token tokenizeIdentifier() {
00221 int line = currentLine;
00222 int column = currentColumn;
00223 string result;
00224
00225 while (isalnum(upcomingChar) || upcomingChar == '_' || upcomingChar == '-') {
00226 result.append(1, upcomingChar);
00227 accept();
00228 }
00229
00230 return Token(Token::IDENTIFIER, result, line, column);
00231 }
00232
00233 Token tokenizeSection() {
00234 expectAndAccept('[');
00235 Token sectionName = tokenizeSectionName();
00236 expectAndAccept(']');
00237 return sectionName;
00238 }
00239
00240 Token tokenizeSectionName() {
00241 int line = currentLine;
00242 int column = currentColumn;
00243 string result;
00244
00245
00246 while (isalnum(upcomingChar) || upcomingChar == '_' || upcomingChar == '-') {
00247 result.append(1, upcomingChar);
00248 accept();
00249 }
00250
00251 return Token(Token::SECTION_NAME, result, line, column);
00252 }
00253
00254 Token tokenizeAssignment() {
00255 expectAndAccept('=');
00256 return Token(Token::ASSIGNMENT, "=", currentLine, currentColumn);
00257 }
00258
00259 Token tokenizeText() {
00260 int line = currentLine;
00261 int column = currentColumn;
00262 string result;
00263
00264 while (upcomingChar != '\n' && upcomingChar != EOF) {
00265 result.append(1, upcomingChar);
00266 accept();
00267 }
00268
00269 return Token(Token::TEXT, result, line, column);
00270 }
00271
00272 Token tokenizeKey() {
00273 return tokenizeIdentifier();
00274 }
00275
00276 Token tokenizeValue() {
00277 return tokenizeText();
00278 }
00279
00280 Token tokenizeUnknown() {
00281 int line = currentLine;
00282 int column = currentColumn;
00283 string result;
00284
00285 while (upcomingChar != EOF) {
00286 result.append(1, upcomingChar);
00287 accept();
00288 }
00289
00290 return Token(Token::UNKNOWN, result, line, column);
00291 }
00292
00293 public:
00294 IniFileLexer(const string &fileName) {
00295 currentLine = 1;
00296 currentColumn = 1;
00297 upcomingTokenPtrIsStale = true;
00298 iniFileStream.open(fileName.c_str());
00299 if (iniFileStream.fail()) {
00300 int e = errno;
00301 throw FileSystemException("Cannot open file '" + fileName + "' for reading",
00302 e, fileName);
00303 }
00304 }
00305
00306 ~IniFileLexer() {
00307 iniFileStream.close();
00308 }
00309
00310 int getCurrentLine() {
00311 return currentLine;
00312 }
00313
00314 int getCurrentColumn() {
00315 return currentColumn;
00316 }
00317
00318 TokenPtr peekToken() {
00319 if (upcomingTokenPtrIsStale) {
00320 Token upcomingToken = getToken();
00321 upcomingTokenPtr = make_shared<Token>(upcomingToken);
00322 upcomingTokenPtrIsStale = false;
00323 }
00324
00325 return upcomingTokenPtr;
00326 }
00327
00328 Token getToken() {
00329 if (!upcomingTokenPtrIsStale) {
00330 upcomingTokenPtrIsStale = true;
00331 return *upcomingTokenPtr;
00332 }
00333
00334 while (iniFileStream.good()) {
00335 upcomingChar = (char)iniFileStream.peek();
00336 switch(upcomingChar) {
00337 case '[':
00338 return tokenizeSection();
00339 case '\n':
00340 if (lastAcceptedChar != '\n') {
00341 accept();
00342 return Token(Token::NEWLINE, "\n", currentLine, currentColumn);
00343 } else {
00344 ignore();
00345 break;
00346 }
00347 case ';':
00348
00349 ignoreWhileNotNewLine();
00350 break;
00351 case '=':
00352 return tokenizeAssignment();
00353 case EOF:
00354 return Token(Token::END_OF_FILE, "<END_OF_FILE>", currentLine, currentColumn);
00355 default:
00356 if (isblank(upcomingChar)) {
00357 ignore();
00358 } else {
00359 switch(lastAcceptedChar) {
00360 case '\n':
00361 return tokenizeKey();
00362 case '=':
00363 return tokenizeValue();
00364 default:
00365 return tokenizeUnknown();
00366 }
00367 }
00368 }
00369 }
00370
00371 return Token(Token::END_OF_FILE, "<END_OF_FILE>", currentLine, currentColumn);
00372 }
00373 };
00374
00375 typedef shared_ptr<IniFileSection> IniFileSectionPtr;
00376
00377
00378 class IniFile {
00379 protected:
00380 typedef map<string, IniFileSectionPtr> SectionMap;
00381 string name;
00382 SectionMap sections;
00383
00384 class IniFileParser {
00385 typedef IniFileLexer::Token Token;
00386
00387 protected:
00388 IniFileLexer lexer;
00389 IniFile *iniFile;
00390
00391
00392 void parseSections() {
00393 while ((lexer.peekToken())->kind == Token::SECTION_NAME) {
00394 parseSection();
00395 }
00396 }
00397
00398 void parseSection() {
00399 Token token = acceptAndReturnif(Token::SECTION_NAME);
00400 acceptIfEOL();
00401
00402 string sectionName = token.value;
00403 IniFileSection *section = new IniFileSection(sectionName);
00404 iniFile->addSection(section);
00405
00406 parseSectionBody(section);
00407 }
00408
00409 void parseSectionBody(IniFileSection *currentSection) {
00410 while ((lexer.peekToken())->kind == Token::IDENTIFIER) {
00411 parseKeyValue(currentSection);
00412 }
00413 }
00414
00415 void parseKeyValue(IniFileSection *currentSection) {
00416 Token identifierToken = acceptAndReturnif (Token::IDENTIFIER);
00417 acceptif (Token::ASSIGNMENT);
00418 Token valueToken = acceptAndReturnif (Token::TEXT);
00419 acceptIfEOL();
00420 currentSection->set(identifierToken.value, valueToken.value);
00421 }
00422
00423 void acceptif (Token::Kind expectedKind) {
00424 Token token = lexer.getToken();
00425
00426 if (token.kind != expectedKind) {
00427 throw Token::ExpectanceException(expectedKind, token);
00428 }
00429 }
00430
00431 void acceptIfEOL() {
00432 Token token = lexer.getToken();
00433
00434 if (token.kind != Token::NEWLINE && token.kind != Token::END_OF_FILE) {
00435 throw Token::ExpectanceException(Token::NEWLINE, token);
00436 }
00437 }
00438
00439 Token acceptAndReturnif (Token::Kind expectedKind) {
00440 Token token = lexer.getToken();
00441
00442 if (token.kind != expectedKind) {
00443 throw Token::ExpectanceException(expectedKind, token);
00444 }
00445
00446 return token;
00447 }
00448
00449 public:
00450 IniFileParser(IniFile *iniFile) : lexer(iniFile->getName()), iniFile(iniFile) {
00451 parseSections();
00452 }
00453 };
00454
00455 public:
00456 IniFile(const string &iniFileName)
00457 : name(iniFileName)
00458 {
00459 IniFileParser parser(this);
00460 }
00461
00462 void addSection(IniFileSection *section) {
00463 sections.insert(make_pair(section->getSectionName(), IniFileSectionPtr(section)));
00464 }
00465
00466 IniFileSectionPtr section(const string §ionName) {
00467 SectionMap::iterator it = sections.find(sectionName);
00468 if (it != sections.end()) {
00469 return it->second;
00470 } else {
00471 return IniFileSectionPtr();
00472 }
00473 }
00474
00475 bool hasSection(const string §ionName) const {
00476 return sections.find(sectionName) != sections.end();
00477 }
00478
00479 string getName() const {
00480 return name;
00481 }
00482
00483 void inspect() const {
00484 SectionMap::const_iterator it = sections.begin();
00485 while (it != sections.end()) {
00486 cout << "[" << (it->first) << "]" << endl;
00487 it->second->inspect();
00488
00489 it++;
00490 }
00491 }
00492 };
00493
00494 }
00495
00496 #endif