/* * Copyright (C) 2007 Thiago Macieira * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include "ruleparser.h" #include "CommandLineParser.h" RulesList::RulesList(const QString &filenames) : m_filenames(filenames) { } RulesList::~RulesList() {} void RulesList::load() { foreach(const QString filename, m_filenames.split(',') ) { qDebug() << "Loading rules from:" << filename; Rules *rules = new Rules(filename); m_rules.append(rules); rules->load(); m_allrepositories.append(rules->repositories()); QList matchRules = rules->matchRules(); m_allMatchRules.append( QList(matchRules)); } } const QList RulesList::allRepositories() const { return m_allrepositories; } const QList > RulesList::allMatchRules() const { return m_allMatchRules; } const QList RulesList::rules() const { return m_rules; } Rules::Rules(const QString &fn) : filename(fn) { } Rules::~Rules() { } const QList Rules::repositories() const { return m_repositories; } const QList Rules::matchRules() const { return m_matchRules; } void Rules::load() { load(filename); } void Rules::load(const QString &filename) { qDebug() << "Loading rules from" << filename; // initialize the regexps we will use QRegExp repoLine("create repository\\s+(\\S+)", Qt::CaseInsensitive); QString varRegex("[A-Za-z0-9_]+"); QRegExp matchLine("match\\s+(.*)", Qt::CaseInsensitive); QRegExp matchActionLine("action\\s+(\\w+)", Qt::CaseInsensitive); QRegExp matchRepoLine("repository\\s+(\\S+)", Qt::CaseInsensitive); QRegExp matchBranchLine("branch\\s+(\\S+)", Qt::CaseInsensitive); QRegExp matchRevLine("(min|max) revision (\\d+)", Qt::CaseInsensitive); QRegExp matchAnnotateLine("annotated\\s+(\\S+)", Qt::CaseInsensitive); QRegExp matchPrefixLine("prefix\\s+(\\S+)", Qt::CaseInsensitive); QRegExp declareLine("declare\\s+("+varRegex+")\\s*=\\s*(\\S+)", Qt::CaseInsensitive); QRegExp variableLine("\\$\\{("+varRegex+")(\\|[^}$]*)?\\}", Qt::CaseInsensitive); QRegExp includeLine("include\\s+(.*)", Qt::CaseInsensitive); enum { ReadingNone, ReadingRepository, ReadingMatch } state = ReadingNone; Repository repo; Match match; int lineNumber = 0; QFile file(filename); if (!file.open(QIODevice::ReadOnly)) qFatal("Could not read the rules file: %s", qPrintable(filename)); QTextStream s(&file); QStringList lines = s.readAll().split('\n', QString::KeepEmptyParts); QStringList::iterator it; for(it = lines.begin(); it != lines.end(); ++it) { ++lineNumber; QString origLine = *it; QString line = origLine; int hash = line.indexOf('#'); if (hash != -1) line.truncate(hash); line = line.trimmed(); if (line.isEmpty()) continue; bool isIncludeRule = includeLine.exactMatch(line); if (isIncludeRule) { int index = filename.lastIndexOf("/"); QString includeFile = filename.left( index + 1) + includeLine.cap(1); load(includeFile); } else { while( variableLine.indexIn(line) != -1 ) { QString replacement; if (m_variables.contains(variableLine.cap(1))) { replacement = m_variables[variableLine.cap(1)]; } else { if (variableLine.cap(2).startsWith('|')) { replacement = variableLine.cap(2).mid(1); } else { qFatal("Undeclared variable: %s", qPrintable(variableLine.cap(1))); } } line = line.replace(variableLine, replacement); } if (state == ReadingRepository) { if (matchBranchLine.exactMatch(line)) { Repository::Branch branch; branch.name = matchBranchLine.cap(1); repo.branches += branch; continue; } else if (matchRepoLine.exactMatch(line)) { repo.forwardTo = matchRepoLine.cap(1); continue; } else if (matchPrefixLine.exactMatch(line)) { repo.prefix = matchPrefixLine.cap(1); continue; } else if (line == "end repository") { m_repositories += repo; { // clear out 'repo' Repository temp; std::swap(repo, temp); } state = ReadingNone; continue; } } else if (state == ReadingMatch) { if (matchRepoLine.exactMatch(line)) { match.repository = matchRepoLine.cap(1); continue; } else if (matchBranchLine.exactMatch(line)) { match.branch = matchBranchLine.cap(1); continue; } else if (matchRevLine.exactMatch(line)) { if (matchRevLine.cap(1) == "min") match.minRevision = matchRevLine.cap(2).toInt(); else // must be max match.maxRevision = matchRevLine.cap(2).toInt(); continue; } else if (matchPrefixLine.exactMatch(line)) { match.prefix = matchPrefixLine.cap(1); if( match.prefix.startsWith('/')) match.prefix = match.prefix.mid(1); continue; } else if (matchActionLine.exactMatch(line)) { QString action = matchActionLine.cap(1); if (action == "export") match.action = Match::Export; else if (action == "ignore") match.action = Match::Ignore; else if (action == "recurse") match.action = Match::Recurse; else qFatal("Invalid action \"%s\" on line %d", qPrintable(action), lineNumber); continue; } else if (matchAnnotateLine.exactMatch(line)) { match.annotate = matchAnnotateLine.cap(1) == "true"; continue; } else if (line == "end match") { if (!match.repository.isEmpty()) match.action = Match::Export; m_matchRules += match; Stats::instance()->addRule(match); state = ReadingNone; continue; } } bool isRepositoryRule = repoLine.exactMatch(line); bool isMatchRule = matchLine.exactMatch(line); bool isVariableRule = declareLine.exactMatch(line); if (isRepositoryRule) { // repository rule state = ReadingRepository; repo = Repository(); // clear repo.name = repoLine.cap(1); repo.lineNumber = lineNumber; repo.filename = filename; } else if (isMatchRule) { // match rule state = ReadingMatch; match = Match(); match.rx = QRegExp(matchLine.cap(1), Qt::CaseSensitive, QRegExp::RegExp2); if( !match.rx.isValid() ) qFatal("Malformed regular expression '%s' in file:'%s':%d, Error: %s", qPrintable(matchLine.cap(1)), qPrintable(filename), lineNumber, qPrintable(match.rx.errorString())); match.lineNumber = lineNumber; match.filename = filename; } else if (isVariableRule) { QString variable = declareLine.cap(1); QString value = declareLine.cap(2); m_variables.insert(variable, value); } else { qFatal("Malformed line in rules file: line %d: %s", lineNumber, qPrintable(origLine)); } } } } Stats *Stats::self = 0; class Stats::Private { public: Private(); void printStats() const; void ruleMatched(const Rules::Match &rule, const int rev); void addRule(const Rules::Match &rule); private: QMap m_usedRules; }; Stats::Stats() : d(new Private()) { use = CommandLineParser::instance()->contains("stats"); } Stats::~Stats() { delete d; } void Stats::init() { if(self) delete self; self = new Stats(); } Stats* Stats::instance() { return self; } void Stats::printStats() const { if(use) d->printStats(); } void Stats::ruleMatched(const Rules::Match &rule, const int rev) { if(use) d->ruleMatched(rule, rev); } void Stats::addRule( const Rules::Match &rule) { if(use) d->addRule(rule); } Stats::Private::Private() { } void Stats::Private::printStats() const { printf("\nRule stats\n"); foreach(const QString name, m_usedRules.keys()) { printf("%s was matched %i times\n", qPrintable(name), m_usedRules[name]); } } void Stats::Private::ruleMatched(const Rules::Match &rule, const int rev) { Q_UNUSED(rev); const QString name = rule.info(); if(!m_usedRules.contains(name)) { m_usedRules.insert(name, 1); qWarning() << "WARN: New match rule, should have been added when created."; } else { m_usedRules[name]++; } } void Stats::Private::addRule( const Rules::Match &rule) { const QString name = rule.info(); if(m_usedRules.contains(name)) qWarning() << "WARN: Rule" << name << "was added multiple times."; m_usedRules.insert(name, 0); } #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug s, const Rules::Match &rule) { s.nospace() << rule.info(); return s.space(); } #endif