diff --git a/.github/workflows/check-formatting.yml b/.github/workflows/check-formatting.yml index 4d2659a33..4759c27db 100644 --- a/.github/workflows/check-formatting.yml +++ b/.github/workflows/check-formatting.yml @@ -8,18 +8,19 @@ on: pull_request: jobs: - build: - runs-on: ubuntu-latest - container: - image: ubuntu:19.10 + check: + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2.3.3 - name: apt-get update - run: apt-get update + run: sudo apt-get update - name: Install clang-format - run: apt-get -y install clang-format + run: sudo apt-get -y install clang-format dos2unix - name: Check formatting run: ./tools/check-format.sh + + - name: Check line-endings + run: ./tools/check-line-endings.sh diff --git a/README.md b/README.md index 87c685b8b..798a72175 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ git submodule update --init --recursive The code is formatted using clang format in Qt Creator. [.clang-format](src/.clang-format) contains the style file for clang format. ### Get it automated with QT Creator + Beautifier + Clang Format -1. Download LLVM: https://releases.llvm.org/9.0.0/LLVM-9.0.0-win64.exe +1. Download LLVM: https://github.com/llvm/llvm-project/releases/download/llvmorg-11.0.0/LLVM-11.0.0-win64.exe 2. During the installation, make sure to add it to your path 3. In QT Creator, select `Help` > `About Plugins` > `C++` > `Beautifier` to enable the plugin 4. Restart QT Creator diff --git a/src/common/ChannelChatters.cpp b/src/common/ChannelChatters.cpp index 396c74aa8..ea50bee58 100644 --- a/src/common/ChannelChatters.cpp +++ b/src/common/ChannelChatters.cpp @@ -1,72 +1,72 @@ -#include "ChannelChatters.hpp" - -#include "messages/Message.hpp" -#include "messages/MessageBuilder.hpp" - -namespace chatterino { - -ChannelChatters::ChannelChatters(Channel &channel) - : channel_(channel) -{ -} - -AccessGuard ChannelChatters::accessChatters() const -{ - return this->chatters_.accessConst(); -} - -void ChannelChatters::addRecentChatter(const QString &user) -{ - this->chatters_.access()->insert(user); -} - -void ChannelChatters::addJoinedUser(const QString &user) -{ - auto joinedUsers = this->joinedUsers_.access(); - joinedUsers->append(user); - - if (!this->joinedUsersMergeQueued_) - { - this->joinedUsersMergeQueued_ = true; - - QTimer::singleShot(500, &this->lifetimeGuard_, [this] { - auto joinedUsers = this->joinedUsers_.access(); - - MessageBuilder builder(systemMessage, - "Users joined: " + joinedUsers->join(", ")); - builder->flags.set(MessageFlag::Collapsed); - joinedUsers->clear(); - this->channel_.addMessage(builder.release()); - this->joinedUsersMergeQueued_ = false; - }); - } -} - -void ChannelChatters::addPartedUser(const QString &user) -{ - auto partedUsers = this->partedUsers_.access(); - partedUsers->append(user); - - if (!this->partedUsersMergeQueued_) - { - this->partedUsersMergeQueued_ = true; - - QTimer::singleShot(500, &this->lifetimeGuard_, [this] { - auto partedUsers = this->partedUsers_.access(); - - MessageBuilder builder(systemMessage, - "Users parted: " + partedUsers->join(", ")); - builder->flags.set(MessageFlag::Collapsed); - this->channel_.addMessage(builder.release()); - partedUsers->clear(); - - this->partedUsersMergeQueued_ = false; - }); - } -} -void ChannelChatters::setChatters(UsernameSet &&set) -{ - *this->chatters_.access() = set; -} - -} // namespace chatterino +#include "ChannelChatters.hpp" + +#include "messages/Message.hpp" +#include "messages/MessageBuilder.hpp" + +namespace chatterino { + +ChannelChatters::ChannelChatters(Channel &channel) + : channel_(channel) +{ +} + +AccessGuard ChannelChatters::accessChatters() const +{ + return this->chatters_.accessConst(); +} + +void ChannelChatters::addRecentChatter(const QString &user) +{ + this->chatters_.access()->insert(user); +} + +void ChannelChatters::addJoinedUser(const QString &user) +{ + auto joinedUsers = this->joinedUsers_.access(); + joinedUsers->append(user); + + if (!this->joinedUsersMergeQueued_) + { + this->joinedUsersMergeQueued_ = true; + + QTimer::singleShot(500, &this->lifetimeGuard_, [this] { + auto joinedUsers = this->joinedUsers_.access(); + + MessageBuilder builder(systemMessage, + "Users joined: " + joinedUsers->join(", ")); + builder->flags.set(MessageFlag::Collapsed); + joinedUsers->clear(); + this->channel_.addMessage(builder.release()); + this->joinedUsersMergeQueued_ = false; + }); + } +} + +void ChannelChatters::addPartedUser(const QString &user) +{ + auto partedUsers = this->partedUsers_.access(); + partedUsers->append(user); + + if (!this->partedUsersMergeQueued_) + { + this->partedUsersMergeQueued_ = true; + + QTimer::singleShot(500, &this->lifetimeGuard_, [this] { + auto partedUsers = this->partedUsers_.access(); + + MessageBuilder builder(systemMessage, + "Users parted: " + partedUsers->join(", ")); + builder->flags.set(MessageFlag::Collapsed); + this->channel_.addMessage(builder.release()); + partedUsers->clear(); + + this->partedUsersMergeQueued_ = false; + }); + } +} +void ChannelChatters::setChatters(UsernameSet &&set) +{ + *this->chatters_.access() = set; +} + +} // namespace chatterino diff --git a/src/common/ChannelChatters.hpp b/src/common/ChannelChatters.hpp index a0c5896e1..2aba51da8 100644 --- a/src/common/ChannelChatters.hpp +++ b/src/common/ChannelChatters.hpp @@ -1,37 +1,37 @@ -#pragma once - -#include "common/Channel.hpp" -#include "common/UniqueAccess.hpp" -#include "common/UsernameSet.hpp" - -namespace chatterino { - -class ChannelChatters -{ -public: - ChannelChatters(Channel &channel); - virtual ~ChannelChatters() = default; // add vtable - - AccessGuard accessChatters() const; - - void addRecentChatter(const QString &user); - void addJoinedUser(const QString &user); - void addPartedUser(const QString &user); - void setChatters(UsernameSet &&set); - -private: - Channel &channel_; - - // maps 2 char prefix to set of names - UniqueAccess chatters_; - - // combines multiple joins/parts into one message - UniqueAccess joinedUsers_; - bool joinedUsersMergeQueued_ = false; - UniqueAccess partedUsers_; - bool partedUsersMergeQueued_ = false; - - QObject lifetimeGuard_; -}; - -} // namespace chatterino +#pragma once + +#include "common/Channel.hpp" +#include "common/UniqueAccess.hpp" +#include "common/UsernameSet.hpp" + +namespace chatterino { + +class ChannelChatters +{ +public: + ChannelChatters(Channel &channel); + virtual ~ChannelChatters() = default; // add vtable + + AccessGuard accessChatters() const; + + void addRecentChatter(const QString &user); + void addJoinedUser(const QString &user); + void addPartedUser(const QString &user); + void setChatters(UsernameSet &&set); + +private: + Channel &channel_; + + // maps 2 char prefix to set of names + UniqueAccess chatters_; + + // combines multiple joins/parts into one message + UniqueAccess joinedUsers_; + bool joinedUsersMergeQueued_ = false; + UniqueAccess partedUsers_; + bool partedUsersMergeQueued_ = false; + + QObject lifetimeGuard_; +}; + +} // namespace chatterino diff --git a/src/common/Credentials.cpp b/src/common/Credentials.cpp index 87abecb98..0812fb039 100644 --- a/src/common/Credentials.cpp +++ b/src/common/Credentials.cpp @@ -1,235 +1,235 @@ -#include "Credentials.hpp" - -#include "debug/AssertInGuiThread.hpp" -#include "keychain.h" -#include "singletons/Paths.hpp" -#include "singletons/Settings.hpp" -#include "util/CombinePath.hpp" -#include "util/Overloaded.hpp" - -#include -#include - -#define FORMAT_NAME \ - ([&] { \ - assert(!provider.contains(":")); \ - return QString("chatterino:%1:%2").arg(provider).arg(name_); \ - })() - -namespace chatterino { - -namespace { - bool useKeyring() - { - if (getPaths()->isPortable()) - { - return false; - } - else - { -#ifdef Q_OS_LINUX - return getSettings()->useKeyring; -#else - return true; -#endif - } - } - - // Insecure storage: - QString insecurePath() - { - return combinePath(getPaths()->settingsDirectory, "credentials.json"); - } - - QJsonDocument loadInsecure() - { - QFile file(insecurePath()); - file.open(QIODevice::ReadOnly); - return QJsonDocument::fromJson(file.readAll()); - } - - void storeInsecure(const QJsonDocument &doc) - { - QSaveFile file(insecurePath()); - file.open(QIODevice::WriteOnly); - file.write(doc.toJson()); - file.commit(); - } - - QJsonDocument &insecureInstance() - { - static auto store = loadInsecure(); - return store; - } - - void queueInsecureSave() - { - static bool isQueued = false; - - if (!isQueued) - { - isQueued = true; - QTimer::singleShot(200, qApp, [] { - storeInsecure(insecureInstance()); - isQueued = false; - }); - } - } - - // QKeychain runs jobs asyncronously, so we have to assure that set/erase - // jobs gets executed in order. - struct SetJob { - QString name; - QString credential; - }; - - struct EraseJob { - QString name; - }; - - using Job = boost::variant; - - static std::queue &jobQueue() - { - static std::queue jobs; - return jobs; - } - - static void runNextJob() - { - auto &&queue = jobQueue(); - - if (!queue.empty()) - { - // we were gonna use std::visit here but macos is shit - - auto &&item = queue.front(); - - if (item.which() == 0) // set job - { - auto set = boost::get(item); - auto job = new QKeychain::WritePasswordJob("chatterino"); - job->setAutoDelete(true); - job->setKey(set.name); - job->setTextData(set.credential); - QObject::connect(job, &QKeychain::Job::finished, qApp, - [](auto) { runNextJob(); }); - job->start(); - } - else // erase job - { - auto erase = boost::get(item); - auto job = new QKeychain::DeletePasswordJob("chatterino"); - job->setAutoDelete(true); - job->setKey(erase.name); - QObject::connect(job, &QKeychain::Job::finished, qApp, - [](auto) { runNextJob(); }); - job->start(); - } - - queue.pop(); - } - } - - static void queueJob(Job &&job) - { - auto &&queue = jobQueue(); - - queue.push(std::move(job)); - if (queue.size() == 1) - { - runNextJob(); - } - } -} // namespace - -Credentials &Credentials::instance() -{ - static Credentials creds; - return creds; -} - -Credentials::Credentials() -{ -} - -void Credentials::get(const QString &provider, const QString &name_, - QObject *receiver, - std::function &&onLoaded) -{ - assertInGuiThread(); - - auto name = FORMAT_NAME; - - if (useKeyring()) - { - auto job = new QKeychain::ReadPasswordJob("chatterino"); - job->setAutoDelete(true); - job->setKey(name); - QObject::connect( - job, &QKeychain::Job::finished, receiver, - [job, onLoaded = std::move(onLoaded)](auto) mutable { - onLoaded(job->textData()); - }, - Qt::DirectConnection); - job->start(); - } - else - { - auto &instance = insecureInstance(); - - onLoaded(instance.object().find(name).value().toString()); - } -} - -void Credentials::set(const QString &provider, const QString &name_, - const QString &credential) -{ - assertInGuiThread(); - - /// On linux, we try to use a keychain but show a message to disable it when it fails. - /// XXX: add said message - - auto name = FORMAT_NAME; - - if (useKeyring()) - { - queueJob(SetJob{name, credential}); - } - else - { - auto &instance = insecureInstance(); - - auto obj = instance.object(); - obj[name] = credential; - instance.setObject(obj); - - queueInsecureSave(); - } -} - -void Credentials::erase(const QString &provider, const QString &name_) -{ - assertInGuiThread(); - - auto name = FORMAT_NAME; - - if (useKeyring()) - { - queueJob(EraseJob{name}); - } - else - { - auto &instance = insecureInstance(); - - if (auto it = instance.object().find(name); - it != instance.object().end()) - { - instance.object().erase(it); - } - - queueInsecureSave(); - } -} - -} // namespace chatterino +#include "Credentials.hpp" + +#include "debug/AssertInGuiThread.hpp" +#include "keychain.h" +#include "singletons/Paths.hpp" +#include "singletons/Settings.hpp" +#include "util/CombinePath.hpp" +#include "util/Overloaded.hpp" + +#include +#include + +#define FORMAT_NAME \ + ([&] { \ + assert(!provider.contains(":")); \ + return QString("chatterino:%1:%2").arg(provider).arg(name_); \ + })() + +namespace chatterino { + +namespace { + bool useKeyring() + { + if (getPaths()->isPortable()) + { + return false; + } + else + { +#ifdef Q_OS_LINUX + return getSettings()->useKeyring; +#else + return true; +#endif + } + } + + // Insecure storage: + QString insecurePath() + { + return combinePath(getPaths()->settingsDirectory, "credentials.json"); + } + + QJsonDocument loadInsecure() + { + QFile file(insecurePath()); + file.open(QIODevice::ReadOnly); + return QJsonDocument::fromJson(file.readAll()); + } + + void storeInsecure(const QJsonDocument &doc) + { + QSaveFile file(insecurePath()); + file.open(QIODevice::WriteOnly); + file.write(doc.toJson()); + file.commit(); + } + + QJsonDocument &insecureInstance() + { + static auto store = loadInsecure(); + return store; + } + + void queueInsecureSave() + { + static bool isQueued = false; + + if (!isQueued) + { + isQueued = true; + QTimer::singleShot(200, qApp, [] { + storeInsecure(insecureInstance()); + isQueued = false; + }); + } + } + + // QKeychain runs jobs asyncronously, so we have to assure that set/erase + // jobs gets executed in order. + struct SetJob { + QString name; + QString credential; + }; + + struct EraseJob { + QString name; + }; + + using Job = boost::variant; + + static std::queue &jobQueue() + { + static std::queue jobs; + return jobs; + } + + static void runNextJob() + { + auto &&queue = jobQueue(); + + if (!queue.empty()) + { + // we were gonna use std::visit here but macos is shit + + auto &&item = queue.front(); + + if (item.which() == 0) // set job + { + auto set = boost::get(item); + auto job = new QKeychain::WritePasswordJob("chatterino"); + job->setAutoDelete(true); + job->setKey(set.name); + job->setTextData(set.credential); + QObject::connect(job, &QKeychain::Job::finished, qApp, + [](auto) { runNextJob(); }); + job->start(); + } + else // erase job + { + auto erase = boost::get(item); + auto job = new QKeychain::DeletePasswordJob("chatterino"); + job->setAutoDelete(true); + job->setKey(erase.name); + QObject::connect(job, &QKeychain::Job::finished, qApp, + [](auto) { runNextJob(); }); + job->start(); + } + + queue.pop(); + } + } + + static void queueJob(Job &&job) + { + auto &&queue = jobQueue(); + + queue.push(std::move(job)); + if (queue.size() == 1) + { + runNextJob(); + } + } +} // namespace + +Credentials &Credentials::instance() +{ + static Credentials creds; + return creds; +} + +Credentials::Credentials() +{ +} + +void Credentials::get(const QString &provider, const QString &name_, + QObject *receiver, + std::function &&onLoaded) +{ + assertInGuiThread(); + + auto name = FORMAT_NAME; + + if (useKeyring()) + { + auto job = new QKeychain::ReadPasswordJob("chatterino"); + job->setAutoDelete(true); + job->setKey(name); + QObject::connect( + job, &QKeychain::Job::finished, receiver, + [job, onLoaded = std::move(onLoaded)](auto) mutable { + onLoaded(job->textData()); + }, + Qt::DirectConnection); + job->start(); + } + else + { + auto &instance = insecureInstance(); + + onLoaded(instance.object().find(name).value().toString()); + } +} + +void Credentials::set(const QString &provider, const QString &name_, + const QString &credential) +{ + assertInGuiThread(); + + /// On linux, we try to use a keychain but show a message to disable it when it fails. + /// XXX: add said message + + auto name = FORMAT_NAME; + + if (useKeyring()) + { + queueJob(SetJob{name, credential}); + } + else + { + auto &instance = insecureInstance(); + + auto obj = instance.object(); + obj[name] = credential; + instance.setObject(obj); + + queueInsecureSave(); + } +} + +void Credentials::erase(const QString &provider, const QString &name_) +{ + assertInGuiThread(); + + auto name = FORMAT_NAME; + + if (useKeyring()) + { + queueJob(EraseJob{name}); + } + else + { + auto &instance = insecureInstance(); + + if (auto it = instance.object().find(name); + it != instance.object().end()) + { + instance.object().erase(it); + } + + queueInsecureSave(); + } +} + +} // namespace chatterino diff --git a/src/common/Credentials.hpp b/src/common/Credentials.hpp index 5cf9da920..260109f6a 100644 --- a/src/common/Credentials.hpp +++ b/src/common/Credentials.hpp @@ -1,23 +1,23 @@ -#pragma once - -#include -#include - -namespace chatterino { - -class Credentials -{ -public: - static Credentials &instance(); - - void get(const QString &provider, const QString &name, QObject *receiver, - std::function &&onLoaded); - void set(const QString &provider, const QString &name, - const QString &credential); - void erase(const QString &provider, const QString &name); - -private: - Credentials(); -}; - -} // namespace chatterino +#pragma once + +#include +#include + +namespace chatterino { + +class Credentials +{ +public: + static Credentials &instance(); + + void get(const QString &provider, const QString &name, QObject *receiver, + std::function &&onLoaded); + void set(const QString &provider, const QString &name, + const QString &credential); + void erase(const QString &provider, const QString &name); + +private: + Credentials(); +}; + +} // namespace chatterino diff --git a/src/providers/irc/Irc2.cpp b/src/providers/irc/Irc2.cpp index 2f685be63..1876c383a 100644 --- a/src/providers/irc/Irc2.cpp +++ b/src/providers/irc/Irc2.cpp @@ -1,260 +1,260 @@ -#include "Irc2.hpp" - -#include -#include "common/Credentials.hpp" -#include "common/SignalVectorModel.hpp" -#include "singletons/Paths.hpp" -#include "util/CombinePath.hpp" -#include "util/RapidjsonHelpers.hpp" -#include "util/StandardItemHelper.hpp" - -#include -#include - -namespace chatterino { - -namespace { - QString configPath() - { - return combinePath(getPaths()->settingsDirectory, "irc.json"); - } - - class Model : public SignalVectorModel - { - public: - Model(QObject *parent) - : SignalVectorModel(6, parent) - { - } - - // turn a vector item into a model row - IrcServerData getItemFromRow(std::vector &row, - const IrcServerData &original) - { - return IrcServerData{ - row[0]->data(Qt::EditRole).toString(), // host - row[1]->data(Qt::EditRole).toInt(), // port - row[2]->data(Qt::CheckStateRole).toBool(), // ssl - row[3]->data(Qt::EditRole).toString(), // user - row[4]->data(Qt::EditRole).toString(), // nick - row[5]->data(Qt::EditRole).toString(), // real - original.authType, // authType - original.connectCommands, // connectCommands - original.id, // id - }; - } - - // turns a row in the model into a vector item - void getRowFromItem(const IrcServerData &item, - std::vector &row) - { - setStringItem(row[0], item.host, false); - setStringItem(row[1], QString::number(item.port)); - setBoolItem(row[2], item.ssl); - setStringItem(row[3], item.user); - setStringItem(row[4], item.nick); - setStringItem(row[5], item.real); - } - }; -} // namespace - -inline QString escape(QString str) -{ - return str.replace(":", "::"); -} - -// This returns a unique id for every server which is understandeable in the systems credential manager. -inline QString getCredentialName(const IrcServerData &data) -{ - return escape(QString::number(data.id)) + ":" + escape(data.user) + "@" + - escape(data.host); -} - -void IrcServerData::getPassword( - QObject *receiver, std::function &&onLoaded) const -{ - Credentials::instance().get("irc", getCredentialName(*this), receiver, - std::move(onLoaded)); -} - -void IrcServerData::setPassword(const QString &password) -{ - Credentials::instance().set("irc", getCredentialName(*this), password); -} - -Irc::Irc() -{ - this->connections.itemInserted.connect([this](auto &&args) { - // make sure only one id can only exist for one server - assert(this->servers_.find(args.item.id) == this->servers_.end()); - - // add new server - if (auto ab = this->abandonedChannels_.find(args.item.id); - ab != this->abandonedChannels_.end()) - { - auto server = std::make_unique(args.item, ab->second); - - // set server of abandoned channels - for (auto weak : ab->second) - if (auto shared = weak.lock()) - if (auto ircChannel = - dynamic_cast(shared.get())) - ircChannel->setServer(server.get()); - - // add new server with abandoned channels - this->servers_.emplace(args.item.id, std::move(server)); - this->abandonedChannels_.erase(ab); - } - else - { - // add new server - this->servers_.emplace(args.item.id, - std::make_unique(args.item)); - } - }); - - this->connections.itemRemoved.connect([this](auto &&args) { - // restore - if (auto server = this->servers_.find(args.item.id); - server != this->servers_.end()) - { - auto abandoned = server->second->getChannels(); - - // set server of abandoned servers to nullptr - for (auto weak : abandoned) - if (auto shared = weak.lock()) - if (auto ircChannel = - dynamic_cast(shared.get())) - ircChannel->setServer(nullptr); - - this->abandonedChannels_[args.item.id] = abandoned; - this->servers_.erase(server); - } - - if (args.caller != Irc::noEraseCredentialCaller) - { - Credentials::instance().erase("irc", getCredentialName(args.item)); - } - }); - - this->connections.delayedItemsChanged.connect([this] { this->save(); }); -} - -QAbstractTableModel *Irc::newConnectionModel(QObject *parent) -{ - auto model = new Model(parent); - model->initialize(&this->connections); - return model; -} - -ChannelPtr Irc::getOrAddChannel(int id, QString name) -{ - if (auto server = this->servers_.find(id); server != this->servers_.end()) - { - return server->second->getOrAddChannel(name); - } - else - { - auto channel = std::make_shared(name, nullptr); - - this->abandonedChannels_[id].push_back(channel); - - return std::move(channel); - } -} - -Irc &Irc::instance() -{ - static Irc irc; - return irc; -} - -int Irc::uniqueId() -{ - int i = this->currentId_ + 1; - auto it = this->servers_.find(i); - auto it2 = this->abandonedChannels_.find(i); - - while (it != this->servers_.end() || it2 != this->abandonedChannels_.end()) - { - i++; - it = this->servers_.find(i); - it2 = this->abandonedChannels_.find(i); - } - - return (this->currentId_ = i); -} - -void Irc::save() -{ - QJsonDocument doc; - QJsonObject root; - QJsonArray servers; - - for (auto &&conn : this->connections) - { - QJsonObject obj; - obj.insert("host", conn.host); - obj.insert("port", conn.port); - obj.insert("ssl", conn.ssl); - obj.insert("username", conn.user); - obj.insert("nickname", conn.nick); - obj.insert("realname", conn.real); - obj.insert("connectCommands", - QJsonArray::fromStringList(conn.connectCommands)); - obj.insert("id", conn.id); - obj.insert("authType", int(conn.authType)); - - servers.append(obj); - } - - root.insert("servers", servers); - doc.setObject(root); - - QSaveFile file(configPath()); - file.open(QIODevice::WriteOnly); - file.write(doc.toJson()); - file.commit(); -} - -void Irc::load() -{ - if (this->loaded_) - return; - this->loaded_ = true; - - QString config = configPath(); - QFile file(configPath()); - file.open(QIODevice::ReadOnly); - auto object = QJsonDocument::fromJson(file.readAll()).object(); - - std::unordered_set ids; - - // load servers - for (auto server : object.value("servers").toArray()) - { - auto obj = server.toObject(); - IrcServerData data; - data.host = obj.value("host").toString(data.host); - data.port = obj.value("port").toInt(data.port); - data.ssl = obj.value("ssl").toBool(data.ssl); - data.user = obj.value("username").toString(data.user); - data.nick = obj.value("nickname").toString(data.nick); - data.real = obj.value("realname").toString(data.real); - data.connectCommands = - obj.value("connectCommands").toVariant().toStringList(); - data.id = obj.value("id").toInt(data.id); - data.authType = - IrcAuthType(obj.value("authType").toInt(int(data.authType))); - - // duplicate id's are not allowed :( - if (ids.find(data.id) == ids.end()) - { - ids.insert(data.id); - - this->connections.append(data); - } - } -} - -} // namespace chatterino +#include "Irc2.hpp" + +#include +#include "common/Credentials.hpp" +#include "common/SignalVectorModel.hpp" +#include "singletons/Paths.hpp" +#include "util/CombinePath.hpp" +#include "util/RapidjsonHelpers.hpp" +#include "util/StandardItemHelper.hpp" + +#include +#include + +namespace chatterino { + +namespace { + QString configPath() + { + return combinePath(getPaths()->settingsDirectory, "irc.json"); + } + + class Model : public SignalVectorModel + { + public: + Model(QObject *parent) + : SignalVectorModel(6, parent) + { + } + + // turn a vector item into a model row + IrcServerData getItemFromRow(std::vector &row, + const IrcServerData &original) + { + return IrcServerData{ + row[0]->data(Qt::EditRole).toString(), // host + row[1]->data(Qt::EditRole).toInt(), // port + row[2]->data(Qt::CheckStateRole).toBool(), // ssl + row[3]->data(Qt::EditRole).toString(), // user + row[4]->data(Qt::EditRole).toString(), // nick + row[5]->data(Qt::EditRole).toString(), // real + original.authType, // authType + original.connectCommands, // connectCommands + original.id, // id + }; + } + + // turns a row in the model into a vector item + void getRowFromItem(const IrcServerData &item, + std::vector &row) + { + setStringItem(row[0], item.host, false); + setStringItem(row[1], QString::number(item.port)); + setBoolItem(row[2], item.ssl); + setStringItem(row[3], item.user); + setStringItem(row[4], item.nick); + setStringItem(row[5], item.real); + } + }; +} // namespace + +inline QString escape(QString str) +{ + return str.replace(":", "::"); +} + +// This returns a unique id for every server which is understandeable in the systems credential manager. +inline QString getCredentialName(const IrcServerData &data) +{ + return escape(QString::number(data.id)) + ":" + escape(data.user) + "@" + + escape(data.host); +} + +void IrcServerData::getPassword( + QObject *receiver, std::function &&onLoaded) const +{ + Credentials::instance().get("irc", getCredentialName(*this), receiver, + std::move(onLoaded)); +} + +void IrcServerData::setPassword(const QString &password) +{ + Credentials::instance().set("irc", getCredentialName(*this), password); +} + +Irc::Irc() +{ + this->connections.itemInserted.connect([this](auto &&args) { + // make sure only one id can only exist for one server + assert(this->servers_.find(args.item.id) == this->servers_.end()); + + // add new server + if (auto ab = this->abandonedChannels_.find(args.item.id); + ab != this->abandonedChannels_.end()) + { + auto server = std::make_unique(args.item, ab->second); + + // set server of abandoned channels + for (auto weak : ab->second) + if (auto shared = weak.lock()) + if (auto ircChannel = + dynamic_cast(shared.get())) + ircChannel->setServer(server.get()); + + // add new server with abandoned channels + this->servers_.emplace(args.item.id, std::move(server)); + this->abandonedChannels_.erase(ab); + } + else + { + // add new server + this->servers_.emplace(args.item.id, + std::make_unique(args.item)); + } + }); + + this->connections.itemRemoved.connect([this](auto &&args) { + // restore + if (auto server = this->servers_.find(args.item.id); + server != this->servers_.end()) + { + auto abandoned = server->second->getChannels(); + + // set server of abandoned servers to nullptr + for (auto weak : abandoned) + if (auto shared = weak.lock()) + if (auto ircChannel = + dynamic_cast(shared.get())) + ircChannel->setServer(nullptr); + + this->abandonedChannels_[args.item.id] = abandoned; + this->servers_.erase(server); + } + + if (args.caller != Irc::noEraseCredentialCaller) + { + Credentials::instance().erase("irc", getCredentialName(args.item)); + } + }); + + this->connections.delayedItemsChanged.connect([this] { this->save(); }); +} + +QAbstractTableModel *Irc::newConnectionModel(QObject *parent) +{ + auto model = new Model(parent); + model->initialize(&this->connections); + return model; +} + +ChannelPtr Irc::getOrAddChannel(int id, QString name) +{ + if (auto server = this->servers_.find(id); server != this->servers_.end()) + { + return server->second->getOrAddChannel(name); + } + else + { + auto channel = std::make_shared(name, nullptr); + + this->abandonedChannels_[id].push_back(channel); + + return std::move(channel); + } +} + +Irc &Irc::instance() +{ + static Irc irc; + return irc; +} + +int Irc::uniqueId() +{ + int i = this->currentId_ + 1; + auto it = this->servers_.find(i); + auto it2 = this->abandonedChannels_.find(i); + + while (it != this->servers_.end() || it2 != this->abandonedChannels_.end()) + { + i++; + it = this->servers_.find(i); + it2 = this->abandonedChannels_.find(i); + } + + return (this->currentId_ = i); +} + +void Irc::save() +{ + QJsonDocument doc; + QJsonObject root; + QJsonArray servers; + + for (auto &&conn : this->connections) + { + QJsonObject obj; + obj.insert("host", conn.host); + obj.insert("port", conn.port); + obj.insert("ssl", conn.ssl); + obj.insert("username", conn.user); + obj.insert("nickname", conn.nick); + obj.insert("realname", conn.real); + obj.insert("connectCommands", + QJsonArray::fromStringList(conn.connectCommands)); + obj.insert("id", conn.id); + obj.insert("authType", int(conn.authType)); + + servers.append(obj); + } + + root.insert("servers", servers); + doc.setObject(root); + + QSaveFile file(configPath()); + file.open(QIODevice::WriteOnly); + file.write(doc.toJson()); + file.commit(); +} + +void Irc::load() +{ + if (this->loaded_) + return; + this->loaded_ = true; + + QString config = configPath(); + QFile file(configPath()); + file.open(QIODevice::ReadOnly); + auto object = QJsonDocument::fromJson(file.readAll()).object(); + + std::unordered_set ids; + + // load servers + for (auto server : object.value("servers").toArray()) + { + auto obj = server.toObject(); + IrcServerData data; + data.host = obj.value("host").toString(data.host); + data.port = obj.value("port").toInt(data.port); + data.ssl = obj.value("ssl").toBool(data.ssl); + data.user = obj.value("username").toString(data.user); + data.nick = obj.value("nickname").toString(data.nick); + data.real = obj.value("realname").toString(data.real); + data.connectCommands = + obj.value("connectCommands").toVariant().toStringList(); + data.id = obj.value("id").toInt(data.id); + data.authType = + IrcAuthType(obj.value("authType").toInt(int(data.authType))); + + // duplicate id's are not allowed :( + if (ids.find(data.id) == ids.end()) + { + ids.insert(data.id); + + this->connections.append(data); + } + } +} + +} // namespace chatterino diff --git a/src/providers/irc/Irc2.hpp b/src/providers/irc/Irc2.hpp index 0cdc60c77..c4ae8c733 100644 --- a/src/providers/irc/Irc2.hpp +++ b/src/providers/irc/Irc2.hpp @@ -1,67 +1,67 @@ -#pragma once - -#include -#include - -#include "providers/irc/IrcChannel2.hpp" -#include "providers/irc/IrcServer.hpp" - -class QAbstractTableModel; - -namespace chatterino { - -enum class IrcAuthType { Anonymous, Custom, Pass, Sasl }; - -struct IrcServerData { - QString host; - int port = 6697; - bool ssl = true; - - QString user; - QString nick; - QString real; - - IrcAuthType authType = IrcAuthType::Anonymous; - void getPassword(QObject *receiver, - std::function &&onLoaded) const; - void setPassword(const QString &password); - - QStringList connectCommands; - - int id; -}; - -class Irc -{ -public: - Irc(); - - static Irc &instance(); - - static inline void *const noEraseCredentialCaller = - reinterpret_cast(1); - - SignalVector connections; - QAbstractTableModel *newConnectionModel(QObject *parent); - - ChannelPtr getOrAddChannel(int serverId, QString name); - - void save(); - void load(); - - int uniqueId(); - -private: - int currentId_{}; - bool loaded_{}; - - // Servers have a unique id. - // When a server gets changed it gets removed and then added again. - // So we store the channels of that server in abandonedChannels_ temporarily. - // Or if the server got removed permanently then it's still stored there. - std::unordered_map> servers_; - std::unordered_map>> - abandonedChannels_; -}; - -} // namespace chatterino +#pragma once + +#include +#include + +#include "providers/irc/IrcChannel2.hpp" +#include "providers/irc/IrcServer.hpp" + +class QAbstractTableModel; + +namespace chatterino { + +enum class IrcAuthType { Anonymous, Custom, Pass, Sasl }; + +struct IrcServerData { + QString host; + int port = 6697; + bool ssl = true; + + QString user; + QString nick; + QString real; + + IrcAuthType authType = IrcAuthType::Anonymous; + void getPassword(QObject *receiver, + std::function &&onLoaded) const; + void setPassword(const QString &password); + + QStringList connectCommands; + + int id; +}; + +class Irc +{ +public: + Irc(); + + static Irc &instance(); + + static inline void *const noEraseCredentialCaller = + reinterpret_cast(1); + + SignalVector connections; + QAbstractTableModel *newConnectionModel(QObject *parent); + + ChannelPtr getOrAddChannel(int serverId, QString name); + + void save(); + void load(); + + int uniqueId(); + +private: + int currentId_{}; + bool loaded_{}; + + // Servers have a unique id. + // When a server gets changed it gets removed and then added again. + // So we store the channels of that server in abandonedChannels_ temporarily. + // Or if the server got removed permanently then it's still stored there. + std::unordered_map> servers_; + std::unordered_map>> + abandonedChannels_; +}; + +} // namespace chatterino diff --git a/src/providers/irc/IrcCommands.cpp b/src/providers/irc/IrcCommands.cpp index 87d07c8d9..a423f9189 100644 --- a/src/providers/irc/IrcCommands.cpp +++ b/src/providers/irc/IrcCommands.cpp @@ -1,76 +1,76 @@ -#include "IrcCommands.hpp" - -#include "messages/MessageBuilder.hpp" -#include "providers/irc/IrcChannel2.hpp" -#include "providers/irc/IrcServer.hpp" -#include "util/Overloaded.hpp" -#include "util/QStringHash.hpp" - -namespace chatterino { - -Outcome invokeIrcCommand(const QString &commandName, const QString &allParams, - IrcChannel &channel) -{ - if (!channel.server()) - { - return Failure; - } - - // STATIC MESSAGES - static auto staticMessages = std::unordered_map{ - {"join", "/join is not supported. Press ctrl+r to change the " - "channel. If required use /raw JOIN #channel."}, - {"part", "/part is not supported. Press ctrl+r to change the " - "channel. If required use /raw PART #channel."}, - }; - auto cmd = commandName.toLower(); - - if (auto it = staticMessages.find(cmd); it != staticMessages.end()) - { - channel.addMessage(makeSystemMessage(it->second)); - return Success; - } - - // CUSTOM COMMANDS - auto params = allParams.split(' '); - auto paramsAfter = [&](int i) { return params.mid(i + 1).join(' '); }; - - auto sendRaw = [&](QString str) { channel.server()->sendRawMessage(str); }; - - if (cmd == "msg") - { - sendRaw("PRIVMSG " + params[0] + " :" + paramsAfter(0)); - } - else if (cmd == "away") - { - sendRaw("AWAY" + params[0] + " :" + paramsAfter(0)); - } - else if (cmd == "knock") - { - sendRaw("KNOCK #" + params[0] + " " + paramsAfter(0)); - } - else if (cmd == "kick") - { - if (paramsAfter(1).isEmpty()) - sendRaw("KICK " + params[0] + " " + params[1]); - else - sendRaw("KICK " + params[0] + " " + params[1] + " :" + - paramsAfter(1)); - } - else if (cmd == "wallops") - { - sendRaw("WALLOPS :" + allParams); - } - else if (cmd == "raw") - { - sendRaw(allParams); - } - else - { - sendRaw(cmd.toUpper() + " " + allParams); - } - - return Success; -} - -} // namespace chatterino +#include "IrcCommands.hpp" + +#include "messages/MessageBuilder.hpp" +#include "providers/irc/IrcChannel2.hpp" +#include "providers/irc/IrcServer.hpp" +#include "util/Overloaded.hpp" +#include "util/QStringHash.hpp" + +namespace chatterino { + +Outcome invokeIrcCommand(const QString &commandName, const QString &allParams, + IrcChannel &channel) +{ + if (!channel.server()) + { + return Failure; + } + + // STATIC MESSAGES + static auto staticMessages = std::unordered_map{ + {"join", "/join is not supported. Press ctrl+r to change the " + "channel. If required use /raw JOIN #channel."}, + {"part", "/part is not supported. Press ctrl+r to change the " + "channel. If required use /raw PART #channel."}, + }; + auto cmd = commandName.toLower(); + + if (auto it = staticMessages.find(cmd); it != staticMessages.end()) + { + channel.addMessage(makeSystemMessage(it->second)); + return Success; + } + + // CUSTOM COMMANDS + auto params = allParams.split(' '); + auto paramsAfter = [&](int i) { return params.mid(i + 1).join(' '); }; + + auto sendRaw = [&](QString str) { channel.server()->sendRawMessage(str); }; + + if (cmd == "msg") + { + sendRaw("PRIVMSG " + params[0] + " :" + paramsAfter(0)); + } + else if (cmd == "away") + { + sendRaw("AWAY" + params[0] + " :" + paramsAfter(0)); + } + else if (cmd == "knock") + { + sendRaw("KNOCK #" + params[0] + " " + paramsAfter(0)); + } + else if (cmd == "kick") + { + if (paramsAfter(1).isEmpty()) + sendRaw("KICK " + params[0] + " " + params[1]); + else + sendRaw("KICK " + params[0] + " " + params[1] + " :" + + paramsAfter(1)); + } + else if (cmd == "wallops") + { + sendRaw("WALLOPS :" + allParams); + } + else if (cmd == "raw") + { + sendRaw(allParams); + } + else + { + sendRaw(cmd.toUpper() + " " + allParams); + } + + return Success; +} + +} // namespace chatterino diff --git a/src/providers/irc/IrcCommands.hpp b/src/providers/irc/IrcCommands.hpp index ffdbde023..d6d0dd795 100644 --- a/src/providers/irc/IrcCommands.hpp +++ b/src/providers/irc/IrcCommands.hpp @@ -1,12 +1,12 @@ -#pragma once - -#include "common/Outcome.hpp" - -namespace chatterino { - -class IrcChannel; - -Outcome invokeIrcCommand(const QString &command, const QString ¶ms, - IrcChannel &channel); - -} // namespace chatterino +#pragma once + +#include "common/Outcome.hpp" + +namespace chatterino { + +class IrcChannel; + +Outcome invokeIrcCommand(const QString &command, const QString ¶ms, + IrcChannel &channel); + +} // namespace chatterino diff --git a/src/providers/twitch/IrcMessageHandler.cpp b/src/providers/twitch/IrcMessageHandler.cpp index 78398f3b0..4f6a2695a 100644 --- a/src/providers/twitch/IrcMessageHandler.cpp +++ b/src/providers/twitch/IrcMessageHandler.cpp @@ -1,4 +1,4 @@ -#include "IrcMessageHandler.hpp" +#include "IrcMessageHandler.hpp" #include "Application.hpp" #include "controllers/accounts/AccountController.hpp" diff --git a/src/providers/twitch/TwitchMessageBuilder.hpp b/src/providers/twitch/TwitchMessageBuilder.hpp index 6df45fedc..5481832d3 100644 --- a/src/providers/twitch/TwitchMessageBuilder.hpp +++ b/src/providers/twitch/TwitchMessageBuilder.hpp @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "common/Aliases.hpp" #include "common/Outcome.hpp" diff --git a/src/util/Overloaded.hpp b/src/util/Overloaded.hpp index 7deb38a67..e5bc805f5 100644 --- a/src/util/Overloaded.hpp +++ b/src/util/Overloaded.hpp @@ -1,13 +1,13 @@ -#pragma once - -namespace chatterino { - -template -struct Overloaded : Ts... { - using Ts::operator()...; -}; - -template -Overloaded(Ts...)->Overloaded; - -} // namespace chatterino +#pragma once + +namespace chatterino { + +template +struct Overloaded : Ts... { + using Ts::operator()...; +}; + +template +Overloaded(Ts...) -> Overloaded; + +} // namespace chatterino diff --git a/src/widgets/dialogs/IrcConnectionEditor.cpp b/src/widgets/dialogs/IrcConnectionEditor.cpp index 055b229fe..c24b3ae89 100644 --- a/src/widgets/dialogs/IrcConnectionEditor.cpp +++ b/src/widgets/dialogs/IrcConnectionEditor.cpp @@ -1,97 +1,97 @@ -#include "IrcConnectionEditor.hpp" -#include "ui_IrcConnectionEditor.h" - -namespace chatterino { - -IrcConnectionEditor::IrcConnectionEditor(const IrcServerData &data, bool isAdd, - QWidget *parent) - - : QDialog(parent, Qt::WindowStaysOnTopHint) - , ui_(new Ui::IrcConnectionEditor) - , data_(data) -{ - this->ui_->setupUi(this); - - this->setWindowTitle(QString(isAdd ? "Add " : "Edit ") + "Irc Connection"); - - QObject::connect(this->ui_->userNameLineEdit, &QLineEdit::textChanged, this, - [this](const QString &text) { - this->ui_->nickNameLineEdit->setPlaceholderText(text); - this->ui_->realNameLineEdit->setPlaceholderText(text); - }); - - this->ui_->serverLineEdit->setText(data.host); - this->ui_->portSpinBox->setValue(data.port); - this->ui_->securityCheckBox->setChecked(data.ssl); - this->ui_->userNameLineEdit->setText(data.user); - this->ui_->nickNameLineEdit->setText(data.nick); - this->ui_->realNameLineEdit->setText(data.real); - this->ui_->connectCommandsEditor->setPlainText( - data.connectCommands.join('\n')); - - data.getPassword(this, [this](const QString &password) { - this->ui_->passwordLineEdit->setText(password); - }); - - this->ui_->loginMethodComboBox->setCurrentIndex([&] { - switch (data.authType) - { - case IrcAuthType::Custom: - return 1; - case IrcAuthType::Pass: - return 2; - case IrcAuthType::Sasl: - return 3; - default: - return 0; - } - }()); - - QObject::connect(this->ui_->loginMethodComboBox, - qOverload(&QComboBox::currentIndexChanged), this, - [this](int index) { - if (index == 1) // Custom - { - this->ui_->connectCommandsEditor->setFocus(); - } - }); - - QFont font("Monospace"); - font.setStyleHint(QFont::TypeWriter); - this->ui_->connectCommandsEditor->setFont(font); -} - -IrcConnectionEditor::~IrcConnectionEditor() -{ - delete ui_; -} - -IrcServerData IrcConnectionEditor::data() -{ - auto data = this->data_; - data.host = this->ui_->serverLineEdit->text(); - data.port = this->ui_->portSpinBox->value(); - data.ssl = this->ui_->securityCheckBox->isChecked(); - data.user = this->ui_->userNameLineEdit->text(); - data.nick = this->ui_->nickNameLineEdit->text(); - data.real = this->ui_->realNameLineEdit->text(); - data.connectCommands = - this->ui_->connectCommandsEditor->toPlainText().split('\n'); - data.setPassword(this->ui_->passwordLineEdit->text()); - data.authType = [this] { - switch (this->ui_->loginMethodComboBox->currentIndex()) - { - case 1: - return IrcAuthType::Custom; - case 2: - return IrcAuthType::Pass; - case 3: - return IrcAuthType::Sasl; - default: - return IrcAuthType::Anonymous; - } - }(); - return data; -} - -} // namespace chatterino +#include "IrcConnectionEditor.hpp" +#include "ui_IrcConnectionEditor.h" + +namespace chatterino { + +IrcConnectionEditor::IrcConnectionEditor(const IrcServerData &data, bool isAdd, + QWidget *parent) + + : QDialog(parent, Qt::WindowStaysOnTopHint) + , ui_(new Ui::IrcConnectionEditor) + , data_(data) +{ + this->ui_->setupUi(this); + + this->setWindowTitle(QString(isAdd ? "Add " : "Edit ") + "Irc Connection"); + + QObject::connect(this->ui_->userNameLineEdit, &QLineEdit::textChanged, this, + [this](const QString &text) { + this->ui_->nickNameLineEdit->setPlaceholderText(text); + this->ui_->realNameLineEdit->setPlaceholderText(text); + }); + + this->ui_->serverLineEdit->setText(data.host); + this->ui_->portSpinBox->setValue(data.port); + this->ui_->securityCheckBox->setChecked(data.ssl); + this->ui_->userNameLineEdit->setText(data.user); + this->ui_->nickNameLineEdit->setText(data.nick); + this->ui_->realNameLineEdit->setText(data.real); + this->ui_->connectCommandsEditor->setPlainText( + data.connectCommands.join('\n')); + + data.getPassword(this, [this](const QString &password) { + this->ui_->passwordLineEdit->setText(password); + }); + + this->ui_->loginMethodComboBox->setCurrentIndex([&] { + switch (data.authType) + { + case IrcAuthType::Custom: + return 1; + case IrcAuthType::Pass: + return 2; + case IrcAuthType::Sasl: + return 3; + default: + return 0; + } + }()); + + QObject::connect(this->ui_->loginMethodComboBox, + qOverload(&QComboBox::currentIndexChanged), this, + [this](int index) { + if (index == 1) // Custom + { + this->ui_->connectCommandsEditor->setFocus(); + } + }); + + QFont font("Monospace"); + font.setStyleHint(QFont::TypeWriter); + this->ui_->connectCommandsEditor->setFont(font); +} + +IrcConnectionEditor::~IrcConnectionEditor() +{ + delete ui_; +} + +IrcServerData IrcConnectionEditor::data() +{ + auto data = this->data_; + data.host = this->ui_->serverLineEdit->text(); + data.port = this->ui_->portSpinBox->value(); + data.ssl = this->ui_->securityCheckBox->isChecked(); + data.user = this->ui_->userNameLineEdit->text(); + data.nick = this->ui_->nickNameLineEdit->text(); + data.real = this->ui_->realNameLineEdit->text(); + data.connectCommands = + this->ui_->connectCommandsEditor->toPlainText().split('\n'); + data.setPassword(this->ui_->passwordLineEdit->text()); + data.authType = [this] { + switch (this->ui_->loginMethodComboBox->currentIndex()) + { + case 1: + return IrcAuthType::Custom; + case 2: + return IrcAuthType::Pass; + case 3: + return IrcAuthType::Sasl; + default: + return IrcAuthType::Anonymous; + } + }(); + return data; +} + +} // namespace chatterino diff --git a/src/widgets/dialogs/IrcConnectionEditor.hpp b/src/widgets/dialogs/IrcConnectionEditor.hpp index 958b2a2f5..e71bb1f1a 100644 --- a/src/widgets/dialogs/IrcConnectionEditor.hpp +++ b/src/widgets/dialogs/IrcConnectionEditor.hpp @@ -1,32 +1,32 @@ -#pragma once - -#include - -#include "providers/irc/Irc2.hpp" -#include "widgets/BaseWindow.hpp" - -namespace Ui { -class IrcConnectionEditor; -} - -namespace chatterino { - -struct IrcServerData; - -class IrcConnectionEditor : public QDialog -{ - Q_OBJECT - -public: - explicit IrcConnectionEditor(const IrcServerData &data, bool isAdd = false, - QWidget *parent = nullptr); - ~IrcConnectionEditor(); - - IrcServerData data(); - -private: - Ui::IrcConnectionEditor *ui_; - IrcServerData data_; -}; - -} // namespace chatterino +#pragma once + +#include + +#include "providers/irc/Irc2.hpp" +#include "widgets/BaseWindow.hpp" + +namespace Ui { +class IrcConnectionEditor; +} + +namespace chatterino { + +struct IrcServerData; + +class IrcConnectionEditor : public QDialog +{ + Q_OBJECT + +public: + explicit IrcConnectionEditor(const IrcServerData &data, bool isAdd = false, + QWidget *parent = nullptr); + ~IrcConnectionEditor(); + + IrcServerData data(); + +private: + Ui::IrcConnectionEditor *ui_; + IrcServerData data_; +}; + +} // namespace chatterino diff --git a/tools/check-format.sh b/tools/check-format.sh index 1af2b596a..e7722ed6f 100755 --- a/tools/check-format.sh +++ b/tools/check-format.sh @@ -4,12 +4,14 @@ set -eu fail="0" +clang-format --version + while read -r file; do if ! diff -u <(cat "$file") <(clang-format "$file"); then echo "$file differs!!!!!!!" fail="1" fi -done < <(find src/ \( -iname "*.hpp" -o -iname "*.cpp" \)) +done < <(find src/ -type f \( -iname "*.hpp" -o -iname "*.cpp" \)) if [ "$fail" = "1" ]; then echo "At least one file is poorly formatted - check the output above" diff --git a/tools/check-line-endings.sh b/tools/check-line-endings.sh new file mode 100755 index 000000000..92ae21b81 --- /dev/null +++ b/tools/check-line-endings.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -eu + +fail="0" + +dos2unix --version + +while read -r file; do + num_dos_line_endings=$(dos2unix -id "$file" | awk '/[0-9]+/{print $(NF-1)}') + if [ "$num_dos_line_endings" -gt "0" ]; then + >&2 echo "File '$file' contains $num_dos_line_endings DOS line-endings, it should only be using unix line-endings!" + fail="1" + fi +done < <(find src/ -type f \( -iname "*.hpp" -o -iname "*.cpp" \)) + +if [ "$fail" = "1" ]; then + >&2 echo "At least one file is not using unix line-endings - check the output above" + exit 1 +fi + +>&2 echo "Every file seems to be using unix line-endings. Good job!"