Fix filename and linenumber for included rulefiles.
[svn2git:svn2git.git] / src / ruleparser.cpp
1 /*
2  *  Copyright (C) 2007  Thiago Macieira <thiago@kde.org>
3  *
4  *  This program is free software: you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation, either version 3 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program 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 General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 #include <QTextStream>
19 #include <QList>
20 #include <QFile>
21 #include <QDebug>
22
23 #include "ruleparser.h"
24
25 RulesList::RulesList(const QString &filenames)
26   : m_filenames(filenames)
27 {
28 }
29
30 RulesList::~RulesList() {}
31
32 void RulesList::load()
33 {
34     foreach(const QString filename, m_filenames.split(',') ) {
35         qDebug() << "Loading rules from:" << filename;
36         Rules *rules = new Rules(filename);
37         m_rules.append(rules);
38         rules->load();
39         m_allrepositories.append(rules->repositories());
40         QList<Rules::Match> matchRules = rules->matchRules();
41         m_allMatchRules.append( QList<Rules::Match>(matchRules));
42     }
43 }
44
45 const QList<Rules::Repository> RulesList::allRepositories() const
46 {
47   return m_allrepositories;
48 }
49
50 const QList<QList<Rules::Match> > RulesList::allMatchRules() const
51 {
52   return m_allMatchRules;
53 }
54
55 const QList<Rules*> RulesList::rules() const
56 {
57   return m_rules;
58 }
59
60 Rules::Rules(const QString &fn)
61     : filename(fn)
62 {
63 }
64
65 Rules::~Rules()
66 {
67 }
68
69 const QList<Rules::Repository> Rules::repositories() const
70 {
71     return m_repositories;
72 }
73
74 const QList<Rules::Match> Rules::matchRules() const
75 {
76     return m_matchRules;
77 }
78
79 void Rules::load()
80 {
81     load(filename);
82 }
83
84 void Rules::load(const QString &filename)
85 {
86     qDebug() << "Loading rules from" << filename;
87     // initialize the regexps we will use
88     QRegExp repoLine("create repository\\s+(\\S+)", Qt::CaseInsensitive);
89
90     QRegExp matchLine("match\\s+(.*)", Qt::CaseInsensitive);
91     QRegExp matchActionLine("action\\s+(\\w+)", Qt::CaseInsensitive);
92     QRegExp matchRepoLine("repository\\s+(\\S+)", Qt::CaseInsensitive);
93     QRegExp matchBranchLine("branch\\s+(\\S+)", Qt::CaseInsensitive);
94     QRegExp matchRevLine("(min|max) revision (\\d+)", Qt::CaseInsensitive);
95     QRegExp matchAnnotateLine("annotated\\s+(\\S+)", Qt::CaseInsensitive);
96     QRegExp matchPrefixLine("prefix\\s+(\\S+)", Qt::CaseInsensitive);
97     QRegExp declareLine("declare\\s+(\\S+)\\s*=\\s*(\\S+)", Qt::CaseInsensitive);
98     QRegExp variableLine("\\$\\{(\\S+)\\}", Qt::CaseInsensitive);
99     QRegExp includeLine("include\\s+(.*)", Qt::CaseInsensitive);
100
101     QMap<QString,QString> variables;
102
103     enum { ReadingNone, ReadingRepository, ReadingMatch } state = ReadingNone;
104     Repository repo;
105     Match match;
106     int lineNumber = 0;
107
108     QFile file(filename);
109     if (!file.open(QIODevice::ReadOnly))
110         qFatal("Could not read the rules file: %s", qPrintable(filename));
111
112     QTextStream s(&file);
113     QStringList lines = s.readAll().split('\n', QString::KeepEmptyParts);
114
115     QStringList::iterator it;
116     for(it = lines.begin(); it != lines.end(); ++it) {
117         ++lineNumber;
118         QString origLine = *it;
119         QString line = origLine;
120
121         int hash = line.indexOf('#');
122         if (hash != -1)
123             line.truncate(hash);
124         line = line.trimmed();
125         if (line.isEmpty())
126             continue;
127
128         bool isIncludeRule = includeLine.exactMatch(line);
129         if (isIncludeRule) {
130             int index = filename.lastIndexOf("/");
131             QString includeFile = filename.left( index + 1) + includeLine.cap(1);
132             load(includeFile);
133         } else {
134             int index = variableLine.indexIn(line);
135             if ( index != -1 ) {
136                 if (!variables.contains(variableLine.cap(1)))
137                     qFatal("Undeclared variable: %s", qPrintable(variableLine.cap(1)));
138                 line = line.replace(variableLine, variables[variableLine.cap(1)]);
139             }
140             if (state == ReadingRepository) {
141                 if (matchBranchLine.exactMatch(line)) {
142                     Repository::Branch branch;
143                     branch.name = matchBranchLine.cap(1);
144
145                     repo.branches += branch;
146                     continue;
147                 } else if (matchRepoLine.exactMatch(line)) {
148                     repo.forwardTo = matchRepoLine.cap(1);
149                     continue;
150                 } else if (matchPrefixLine.exactMatch(line)) {
151                     repo.prefix = matchPrefixLine.cap(1);
152                     continue;
153                 } else if (line == "end repository") {
154                     m_repositories += repo;
155                     {
156                         // clear out 'repo'
157                         Repository temp;
158                         std::swap(repo, temp);
159                     }
160                     state = ReadingNone;
161                     continue;
162                 }
163             } else if (state == ReadingMatch) {
164                 if (matchRepoLine.exactMatch(line)) {
165                     match.repository = matchRepoLine.cap(1);
166                     continue;
167                 } else if (matchBranchLine.exactMatch(line)) {
168                     match.branch = matchBranchLine.cap(1);
169                     continue;
170                 } else if (matchRevLine.exactMatch(line)) {
171                     if (matchRevLine.cap(1) == "min")
172                         match.minRevision = matchRevLine.cap(2).toInt();
173                     else            // must be max
174                         match.maxRevision = matchRevLine.cap(2).toInt();
175                     continue;
176                 } else if (matchPrefixLine.exactMatch(line)) {
177                     match.prefix = matchPrefixLine.cap(1);
178                     if( match.prefix.startsWith('/'))
179                         match.prefix = match.prefix.mid(1);
180                     continue;
181                 } else if (matchActionLine.exactMatch(line)) {
182                     QString action = matchActionLine.cap(1);
183                     if (action == "export")
184                         match.action = Match::Export;
185                     else if (action == "ignore")
186                         match.action = Match::Ignore;
187                     else if (action == "recurse")
188                         match.action = Match::Recurse;
189                     else
190                         qFatal("Invalid action \"%s\" on line %d", qPrintable(action), lineNumber);
191                     continue;
192                 } else if (matchAnnotateLine.exactMatch(line)) {
193                     match.annotate = matchAnnotateLine.cap(1) == "true";
194                     continue;
195                 } else if (line == "end match") {
196                     if (!match.repository.isEmpty())
197                         match.action = Match::Export;
198                     m_matchRules += match;
199                     state = ReadingNone;
200                     continue;
201                 }
202             }
203
204             bool isRepositoryRule = repoLine.exactMatch(line);
205             bool isMatchRule = matchLine.exactMatch(line);
206             bool isVariableRule = declareLine.exactMatch(line);
207
208             if (isRepositoryRule) {
209                 // repository rule
210                 state = ReadingRepository;
211                 repo = Repository(); // clear
212                 repo.name = repoLine.cap(1);
213                 repo.lineNumber = lineNumber;
214                 repo.filename = filename;
215             } else if (isMatchRule) {
216                 // match rule
217                 state = ReadingMatch;
218                 match = Match();
219                 match.rx = QRegExp(matchLine.cap(1), Qt::CaseSensitive, QRegExp::RegExp2);
220                 if( !match.rx.isValid() )
221                     qFatal("Malformed regular expression '%s' in file:'%s':%d, Error: %s",
222                            qPrintable(matchLine.cap(1)), qPrintable(filename), lineNumber,
223                            qPrintable(match.rx.errorString()));
224                 match.lineNumber = lineNumber;
225                 match.filename = filename;
226             } else if (isVariableRule) {
227                 QString variable = declareLine.cap(1);
228                 QString value = declareLine.cap(2);
229                 variables.insert(variable, value);
230             } else {
231                 qFatal("Malformed line in rules file: line %d: %s",
232                        lineNumber, qPrintable(origLine));
233             }
234         }
235     }
236 }
237
238 #ifndef QT_NO_DEBUG_STREAM
239 QDebug operator<<(QDebug s, const Rules::Match &rule)
240 {
241     s.nospace() << rule.info();
242     return s.space();
243 }
244
245 #endif