Allow comments (lines starting with # character) and blank lines in openmw.cfg. Should resolve Feature #2535.

- allows moving various config entries up or down
- comment lines above config entries stay as a pair
c++11
cc9cii 10 years ago
parent 0efe8f5465
commit c22c9c271d

@ -490,7 +490,7 @@ bool Launcher::MainDialog::writeSettings()
// Game settings
QFile file(userPath + QString("openmw.cfg"));
if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) {
if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) {
// File cannot be opened or created
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Error writing OpenMW configuration file"));
@ -503,10 +503,8 @@ bool Launcher::MainDialog::writeSettings()
return false;
}
QTextStream stream(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
mGameSettings.writeFile(stream);
mGameSettings.writeFileWithComments(file);
file.close();
// Graphics settings
@ -525,6 +523,7 @@ bool Launcher::MainDialog::writeSettings()
return false;
}
QTextStream stream(&file);
stream.setDevice(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));

@ -1,6 +1,7 @@
#include "gamesettings.hpp"
#include "launchersettings.hpp"
#include <QTextCodec>
#include <QTextStream>
#include <QDir>
#include <QString>
@ -173,6 +174,214 @@ bool Config::GameSettings::writeFile(QTextStream &stream)
return true;
}
bool Config::GameSettings::isOrderedLine(const QString& line) const
{
return line.contains(QRegExp("^\\s*fallback-archive\\s*="))
|| line.contains(QRegExp("^\\s*fallback\\s*="))
|| line.contains(QRegExp("^\\s*data\\s*="))
|| line.contains(QRegExp("^\\s*data-local\\s*="))
|| line.contains(QRegExp("^\\s*resources\\s*="))
|| line.contains(QRegExp("^\\s*content\\s*="));
}
// Policy:
//
// - Always ignore a line beginning with '#' or empty lines; added above a config
// entry.
//
// - If a line in file exists with matching key and first part of value (before ',',
// '\n', etc) also matches, then replace the line with that of mUserSettings.
// - else remove line
//
// - If there is no corresponding line in file, add at the end
//
bool Config::GameSettings::writeFileWithComments(QFile &file)
{
QTextStream stream(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
// slurp
std::vector<QString> fileCopy;
QString line = stream.readLine();
while (!line.isNull())
{
fileCopy.push_back(line);
line = stream.readLine();
}
stream.seek(0);
// empty file, no comments to keep
if (fileCopy.empty())
return writeFile(stream);
// start
// |
// | +----------------------------------------------------------+
// | | |
// v v |
// skip non-"ordered" lines (remove "ordered" lines) |
// | ^ |
// | | |
// | non-"ordered" line, write saved comments |
// | ^ |
// v | |
// blank or comment line, save in temp buffer <--------+ |
// | | | |
// v +------- comment line ------+ |
// "ordered" line |
// | |
// v |
// save in a separate map of comments keyed by "ordered" line |
// | |
// +----------------------------------------------------------+
//
//
QRegExp settingRegex("^([^=]+)\\s*=\\s*([^,]+)(.*)$");
std::vector<QString> comments;
std::vector<QString>::iterator commentStart = fileCopy.end();
std::map<QString, std::vector<QString> > commentsMap;
for (std::vector<QString>::iterator iter = fileCopy.begin(); iter != fileCopy.end(); ++iter)
{
if (isOrderedLine(*iter))
{
// save in a separate map of comments keyed by "ordered" line
if (!comments.empty())
{
if (settingRegex.indexIn(*iter) != -1)
{
commentsMap[settingRegex.cap(1)+"="+settingRegex.cap(2)] = comments;
comments.clear();
commentStart = fileCopy.end();
}
// else do nothing, malformed line
}
*iter = QString(); // "ordered" lines to be removed later
}
else if ((*iter).isEmpty() || (*iter).contains(QRegExp("^\\s*#")))
{
// comment line, save in temp buffer
if (comments.empty())
commentStart = iter;
comments.push_back(*iter);
*iter = QString(); // assume to be deleted later
}
else
{
int index = settingRegex.indexIn(*iter);
// blank or non-"ordered" line, write saved comments
if (!comments.empty() && index != -1 && settingRegex.captureCount() >= 2 &&
mUserSettings.find(settingRegex.cap(1)) != mUserSettings.end())
{
for (std::vector<QString>::const_iterator it = comments.begin(); it != comments.end(); ++it)
{
*commentStart = *it;
++commentStart;
}
comments.clear();
commentStart = fileCopy.end();
}
// keep blank lines and non-"ordered" lines other than comments
// look for a key in the line
if (index == -1 || settingRegex.captureCount() < 2)
{
// no key or first part of value found in line, replace with a null string which
// will be remved later
*iter = QString();
comments.clear();
commentStart = fileCopy.end();
continue;
}
// look for a matching key in user settings
*iter = QString(); // assume no match
QString key = settingRegex.cap(1);
QString keyVal = settingRegex.cap(1)+"="+settingRegex.cap(2);
QMap<QString, QString>::const_iterator i = mUserSettings.find(key);
while (i != mUserSettings.end() && i.key() == key)
{
QString settingLine = i.key() + "=" + i.value();
if (settingRegex.indexIn(settingLine) != -1)
{
if ((settingRegex.cap(1)+"="+settingRegex.cap(2)) == keyVal)
{
*iter = settingLine;
break;
}
}
++i;
}
}
}
// comments at top of file
for (std::vector<QString>::iterator iter = fileCopy.begin(); iter != fileCopy.end(); ++iter)
{
if ((*iter).isNull())
continue;
// Below is based on readFile() code, if that changes corresponding change may be
// required (for example duplicates may be inserted if the rules don't match)
if (/*(*iter).isEmpty() ||*/ (*iter).contains(QRegExp("^\\s*#")))
{
stream << *iter << "\n";
continue;
}
}
// Iterate in reverse order to preserve insertion order
QString settingLine;
QMapIterator<QString, QString> it(mUserSettings);
it.toBack();
while (it.hasPrevious())
{
it.previous();
// Quote paths with spaces
if ((it.key() == QLatin1String("data")
|| it.key() == QLatin1String("data-local")
|| it.key() == QLatin1String("resources")) && it.value().contains(QChar(' ')))
{
QString stripped = it.value();
stripped.remove(QChar('\"')); // Remove quotes
settingLine = it.key() + "=\"" + stripped + "\"";
}
else
settingLine = it.key() + "=" + it.value();
if (settingRegex.indexIn(settingLine) != -1)
{
std::map<QString, std::vector<QString> >::const_iterator i =
commentsMap.find(settingRegex.cap(1)+"="+settingRegex.cap(2));
if (i != commentsMap.end())
{
std::vector<QString> cLines = i->second;
for (std::vector<QString>::const_iterator ci = cLines.begin(); ci != cLines.end(); ++ci)
stream << *ci << "\n";
}
}
stream << settingLine << "\n";
}
// flush any end comments
if (!comments.empty())
{
for (std::vector<QString>::const_iterator ci = comments.begin(); ci != comments.end(); ++ci)
stream << *ci << "\n";
}
file.resize(file.pos());
return true;
}
bool Config::GameSettings::hasMaster()
{
bool result = false;

@ -4,6 +4,7 @@
#include <QTextStream>
#include <QStringList>
#include <QString>
#include <QFile>
#include <QMap>
#include <boost/filesystem/path.hpp>
@ -66,6 +67,7 @@ namespace Config
bool readUserFile(QTextStream &stream);
bool writeFile(QTextStream &stream);
bool writeFileWithComments(QFile &file);
void setContentList(const QStringList& fileNames);
QStringList getContentList() const;
@ -81,6 +83,8 @@ namespace Config
QString mDataLocal;
static const char sContentKey[];
bool isOrderedLine(const QString& line) const;
};
}
#endif // GAMESETTINGS_HPP

Loading…
Cancel
Save