Passenger: IniFile.h Source File

- Main Page
- Modules
- Namespaces
- Classes
- Files

- File List

# IniFile.h

    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 &sectionName) {
    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                 // String representations of the Kind enum.
    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                 //while (upcomingChar != ']' && upcomingChar != '[' && upcomingChar != '\n' && upcomingChar != EOF) {
    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                                         // Comment encountered: accept all characters until newline (exclusive).
    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                 // The Start Symbol.
    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 &sectionName) {
    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 &sectionName) 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 } // namespace Passenger
    00495
    00496 #endif /* _PASSENGER_INI_FILE_H_ */