Support 'description' field in the create repository rule.
[svn2git:sdoerners-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 #include "CommandLineParser.h"
25
26 RulesList::RulesList(const QString &filenames)
27   : m_filenames(filenames)
28 {
29 }
30
31 RulesList::~RulesList() {}
32
33 void RulesList::load()
34 {
35     foreach(const QString filename, m_filenames.split(',') ) {
36         qDebug() << "Loading rules from:" << filename;
37         Rules *rules = new Rules(filename);
38         m_rules.append(rules);
39         rules->load();
40         m_allrepositories.append(rules->repositories());
41         QList<Rules::Match> matchRules = rules->matchRules();
42         m_allMatchRules.append( QList<Rules::Match>(matchRules));
43     }
44 }
45
46 const QList<Rules::Repository> RulesList::allRepositories() const
47 {
48   return m_allrepositories;
49 }
50
51 const QList<QList<Rules::Match> > RulesList::allMatchRules() const
52 {
53   return m_allMatchRules;
54 }
55
56 const QList<Rules*> RulesList::rules() const
57 {
58   return m_rules;
59 }
60
61 Rules::Rules(const QString &fn)
62     : filename(fn)
63 {
64 }
65
66 Rules::~Rules()
67 {
68 }
69
70 const QList<Rules::Repository> Rules::repositories() const
71 {
72     return m_repositories;
73 }
74
75 const QList<Rules::Match> Rules::matchRules() const
76 {
77     return m_matchRules;
78 }
79
80 void Rules::load()
81 {
82     load(filename);
83 }
84
85 void Rules::load(const QString &filename)
86 {
87     qDebug() << "Loading rules from" << filename;
88     // initialize the regexps we will use
89     QRegExp repoLine("create repository\\s+(\\S+)", Qt::CaseInsensitive);
90
91     QString varRegex("[A-Za-z0-9_]+");
92
93     QRegExp matchLine("match\\s+(.*)", Qt::CaseInsensitive);
94     QRegExp matchActionLine("action\\s+(\\w+)", Qt::CaseInsensitive);
95     QRegExp matchRepoLine("repository\\s+(\\S+)", Qt::CaseInsensitive);
96     QRegExp matchDescLine("description\\s+(.+)$", Qt::CaseInsensitive);
97     QRegExp matchBranchLine("branch\\s+(\\S+)", Qt::CaseInsensitive);
98     QRegExp matchRevLine("(min|max) revision (\\d+)", Qt::CaseInsensitive);
99     QRegExp matchAnnotateLine("annotated\\s+(\\S+)", Qt::CaseInsensitive);
100     QRegExp matchPrefixLine("prefix\\s+(\\S+)", Qt::CaseInsensitive);
101     QRegExp declareLine("declare\\s+("+varRegex+")\\s*=\\s*(\\S+)", Qt::CaseInsensitive);
102     QRegExp variableLine("\\$\\{("+varRegex+")(\\|[^}$]*)?\\}", Qt::CaseInsensitive);
103     QRegExp includeLine("include\\s+(.*)", Qt::CaseInsensitive);
104
105     enum { ReadingNone, ReadingRepository, ReadingMatch } state = ReadingNone;
106     Repository repo;
107     Match match;
108     int lineNumber = 0;
109
110     QFile file(filename);
111     if (!file.open(QIODevice::ReadOnly))
112         qFatal("Could not read the rules file: %s", qPrintable(filename));
113
114     QTextStream s(&file);
115     QStringList lines = s.readAll().split('\n', QString::KeepEmptyParts);
116
117     QStringList::iterator it;
118     for(it = lines.begin(); it != lines.end(); ++it) {
119         ++lineNumber;
120         QString origLine = *it;
121         QString line = origLine;
122
123         int hash = line.indexOf('#');
124         if (hash != -1)
125             line.truncate(hash);
126         line = line.trimmed();
127         if (line.isEmpty())
128             continue;
129
130         bool isIncludeRule = includeLine.exactMatch(line);
131         if (isIncludeRule) {
132             int index = filename.lastIndexOf("/");
133             QString includeFile = filename.left( index + 1) + includeLine.cap(1);
134             load(includeFile);
135         } else {
136             while( variableLine.indexIn(line) != -1 ) {
137                 QString replacement;
138                 if (m_variables.contains(variableLine.cap(1))) {
139                     replacement = m_variables[variableLine.cap(1)];
140                 } else {
141                     if (variableLine.cap(2).startsWith('|')) {
142                         replacement = variableLine.cap(2).mid(1);
143                     } else {
144                         qFatal("Undeclared variable: %s", qPrintable(variableLine.cap(1)));
145                     }
146                 }
147                 line = line.replace(variableLine, replacement);
148             }
149             if (state == ReadingRepository) {
150                 if (matchBranchLine.exactMatch(line)) {
151                     Repository::Branch branch;
152                     branch.name = matchBranchLine.cap(1);
153
154                     repo.branches += branch;
155                     continue;
156                 } else if (matchDescLine.exactMatch(line)) {
157                     repo.description = matchDescLine.cap(1);
158                     continue;
159                 } else if (matchRepoLine.exactMatch(line)) {
160                     repo.forwardTo = matchRepoLine.cap(1);
161                     continue;
162                 } else if (matchPrefixLine.exactMatch(line)) {
163                     repo.prefix = matchPrefixLine.cap(1);
164                     continue;
165                 } else if (line == "end repository") {
166                     m_repositories += repo;
167                     {
168                         // clear out 'repo'
169                         Repository temp;
170                         std::swap(repo, temp);
171                     }
172                     state = ReadingNone;
173                     continue;
174                 }
175             } else if (state == ReadingMatch) {
176                 if (matchRepoLine.exactMatch(line)) {
177                     match.repository = matchRepoLine.cap(1);
178                     continue;
179                 } else if (matchBranchLine.exactMatch(line)) {
180                     match.branch = matchBranchLine.cap(1);
181                     continue;
182                 } else if (matchRevLine.exactMatch(line)) {
183                     if (matchRevLine.cap(1) == "min")
184                         match.minRevision = matchRevLine.cap(2).toInt();
185                     else            // must be max
186                         match.maxRevision = matchRevLine.cap(2).toInt();
187                     continue;
188                 } else if (matchPrefixLine.exactMatch(line)) {
189                     match.prefix = matchPrefixLine.cap(1);
190                     if( match.prefix.startsWith('/'))
191                         match.prefix = match.prefix.mid(1);
192                     continue;
193                 } else if (matchActionLine.exactMatch(line)) {
194                     QString action = matchActionLine.cap(1);
195                     if (action == "export")
196                         match.action = Match::Export;
197                     else if (action == "ignore")
198                         match.action = Match::Ignore;
199                     else if (action == "recurse")
200                         match.action = Match::Recurse;
201                     else
202                         qFatal("Invalid action \"%s\" on line %d", qPrintable(action), lineNumber);
203                     continue;
204                 } else if (matchAnnotateLine.exactMatch(line)) {
205                     match.annotate = matchAnnotateLine.cap(1) == "true";
206                     continue;
207                 } else if (line == "end match") {
208                     if (!match.repository.isEmpty())
209                         match.action = Match::Export;
210                     m_matchRules += match;
211                     Stats::instance()->addRule(match);
212                     state = ReadingNone;
213                     continue;
214                 }
215             }
216
217             bool isRepositoryRule = repoLine.exactMatch(line);
218             bool isMatchRule = matchLine.exactMatch(line);
219             bool isVariableRule = declareLine.exactMatch(line);
220
221             if (isRepositoryRule) {
222                 // repository rule
223                 state = ReadingRepository;
224                 repo = Repository(); // clear
225                 repo.name = repoLine.cap(1);
226                 repo.lineNumber = lineNumber;
227                 repo.filename = filename;
228             } else if (isMatchRule) {
229                 // match rule
230                 state = ReadingMatch;
231                 match = Match();
232                 match.rx = QRegExp(matchLine.cap(1), Qt::CaseSensitive, QRegExp::RegExp2);
233                 if( !match.rx.isValid() )
234                     qFatal("Malformed regular expression '%s' in file:'%s':%d, Error: %s",
235                            qPrintable(matchLine.cap(1)), qPrintable(filename), lineNumber,
236                            qPrintable(match.rx.errorString()));
237                 match.lineNumber = lineNumber;
238                 match.filename = filename;
239             } else if (isVariableRule) {
240                 QString variable = declareLine.cap(1);
241                 QString value = declareLine.cap(2);
242                 m_variables.insert(variable, value);
243             } else {
244                 qFatal("Malformed line in rules file: line %d: %s",
245                        lineNumber, qPrintable(origLine));
246             }
247         }
248     }
249 }
250
251 Stats *Stats::self = 0;
252
253 class Stats::Private
254 {
255 public:
256     Private();
257
258     void printStats() const;
259     void ruleMatched(const Rules::Match &rule, const int rev);
260     void addRule(const Rules::Match &rule);
261 private:
262     QMap<QString,int> m_usedRules;
263 };
264
265 Stats::Stats() : d(new Private())
266 {
267     use = CommandLineParser::instance()->contains("stats");
268 }
269
270 Stats::~Stats()
271 {
272     delete d;
273 }
274
275 void Stats::init()
276 {
277     if(self)
278         delete self;
279     self = new Stats();
280 }
281
282 Stats* Stats::instance()
283 {
284     return self;
285 }
286
287 void Stats::printStats() const
288 {
289     if(use)
290         d->printStats();
291 }
292
293 void Stats::ruleMatched(const Rules::Match &rule, const int rev)
294 {
295     if(use)
296         d->ruleMatched(rule, rev);
297 }
298
299 void Stats::addRule( const Rules::Match &rule)
300 {
301     if(use)
302         d->addRule(rule);
303 }
304
305 Stats::Private::Private()
306 {
307 }
308
309 void Stats::Private::printStats() const
310 {
311     printf("\nRule stats\n");
312     foreach(const QString name, m_usedRules.keys()) {
313         printf("%s was matched %i times\n", qPrintable(name), m_usedRules[name]);
314     }
315 }
316
317 void Stats::Private::ruleMatched(const Rules::Match &rule, const int rev)
318 {
319     Q_UNUSED(rev);
320     const QString name = rule.info();
321     if(!m_usedRules.contains(name)) {
322         m_usedRules.insert(name, 1);
323         qWarning() << "WARN: New match rule, should have been added when created.";
324     } else {
325         m_usedRules[name]++;
326     }
327 }
328
329 void Stats::Private::addRule( const Rules::Match &rule)
330 {
331     const QString name = rule.info();
332     if(m_usedRules.contains(name))
333         qWarning() << "WARN: Rule" << name << "was added multiple times.";
334     m_usedRules.insert(name, 0);
335 }
336
337 #ifndef QT_NO_DEBUG_STREAM
338 QDebug operator<<(QDebug s, const Rules::Match &rule)
339 {
340     s.nospace() << rule.info();
341     return s.space();
342 }
343
344 #endif