Commit 7e74b0cd authored by Waqar Ahmed's avatar Waqar Ahmed
Browse files

Introduce ByteArraySplitter

Small helper class to split QByteArray on a char. For now splitting only
on char is supported but can be extended to split on a string if needed.

The main reason to include this is to avoid indexOf based iteration that
I am using in many places which makes the code difficult to read. With
this, we can use normal c++ range for to nicely iterate over splitted
parts of the string.
parent b9693dc9
Pipeline #252691 passed with stage
in 13 minutes and 30 seconds
......@@ -5,12 +5,12 @@
*/
#include "gitstatus.h"
#include <bytearraysplitter.h>
#include <gitprocess.h>
#include <KLocalizedString>
#include <QByteArray>
#include <QProcess>
#include <QScopeGuard>
#include <charconv>
#include <optional>
......@@ -41,17 +41,7 @@ GitUtils::GitParsedStatus GitUtils::parseStatus(const QByteArray &raw, bool with
QVector<GitUtils::StatusItem> staged;
QVector<GitUtils::StatusItem> changed;
int start = 0;
int next = raw.indexOf(char(0x0), start);
const char *p = raw.data();
while (next != -1) {
auto _ = qScopeGuard([&raw, &start, &next] {
start = next + 1;
next = raw.indexOf(char(0), start);
});
std::string_view r(p + start, next - start);
for (auto r : ByteArraySplitter(raw, '\0')) {
if (r.length() < 3) {
continue;
}
......@@ -61,8 +51,8 @@ GitUtils::GitParsedStatus GitUtils::parseStatus(const QByteArray &raw, bool with
uint16_t xy = (((uint16_t)x) << 8) | y;
using namespace GitUtils;
std::string_view file_view = r.substr(3);
QByteArray file(file_view.data(), file_view.size());
r.remove_prefix(3);
QByteArray file = r.toByteArray();
switch (xy) {
case StatusXY::QQ:
......@@ -191,22 +181,10 @@ static std::optional<int> toInt(std::string_view s)
void GitUtils::parseDiffNumStat(QVector<GitUtils::StatusItem> &items, const QByteArray &raw)
{
int start = 0;
int next = raw.indexOf(char(0), start);
const char *r = raw.constData();
// format:
// 12\t10\tFileName
// 12 = add, 10 = sub, fileName at the end
while (next != -1) {
auto _ = qScopeGuard([&raw, &start, &next] {
start = next + 1;
next = raw.indexOf(char(0), start);
});
std::string_view line(r + start, next - start);
for (auto line : ByteArraySplitter(raw, '\0')) {
size_t addEnd = line.find_first_of('\t');
if (addEnd == std::string_view::npos) {
continue;
......@@ -232,7 +210,7 @@ void GitUtils::parseDiffNumStat(QVector<GitUtils::StatusItem> &items, const QByt
if (!add.has_value()) {
continue;
}
if (!add.has_value()) {
if (!sub.has_value()) {
continue;
}
......@@ -242,19 +220,21 @@ void GitUtils::parseDiffNumStat(QVector<GitUtils::StatusItem> &items, const QByt
QVector<GitUtils::StatusItem> GitUtils::parseDiffNameStatus(const QByteArray &raw)
{
const auto lines = raw.split('\n');
QVector<GitUtils::StatusItem> out;
out.reserve(lines.size());
for (const auto &l : lines) {
const auto cols = l.split('\t');
if (cols.size() < 2) {
for (auto l : ByteArraySplitter(raw, '\n')) {
ByteArraySplitter splitter(l, '\t');
if (splitter.empty()) {
continue;
}
auto it = splitter.begin();
GitUtils::StatusItem i;
i.statusChar = cols[0][0];
i.statusChar = (*it).at(0);
i.file = cols[1];
++it;
if (it == splitter.end()) {
continue;
}
i.file = (*it).toByteArray();
out.append(i);
}
return out;
......
......@@ -8,6 +8,7 @@
#include "kateprojectworker.h"
#include "kateprojectitem.h"
#include <bytearraysplitter.h>
#include <gitprocess.h>
#include "hostprocess.h"
......@@ -288,7 +289,7 @@ void KateProjectWorker::loadFilesEntry(QStandardItem *parent,
/**
* get list of files for this directory, might query the VCS
*/
QVector<QString> files = findFiles(dir, filesEntry);
const QVector<QString> files = findFiles(dir, filesEntry);
QStringList excludeFolderPatterns = m_projectMap.value(QStringLiteral("exclude_patterns")).toStringList();
std::vector<QRegularExpression> excludeRegexps;
......@@ -480,16 +481,15 @@ QVector<QString> KateProjectWorker::gitFiles(const QDir &dir, bool recursive, co
return files;
}
const QList<QByteArray> byteArrayList = git.readAllStandardOutput().split('\0');
files.reserve(byteArrayList.size());
for (const QByteArray &byteArray : byteArrayList) {
if (byteArray.isEmpty()) {
const QByteArray b = git.readAllStandardOutput();
for (strview byteArray : ByteArraySplitter(b, '\0')) {
if (byteArray.empty()) {
continue;
}
if (!recursive && (byteArray.indexOf('/') != -1)) {
if (!recursive && (byteArray.find('/') != std::string::npos)) {
continue;
}
files.append(QString::fromUtf8(byteArray));
files.append(byteArray.toString());
}
return files;
}
......
......@@ -26,4 +26,5 @@ kate_executable_tests(
json_utils_test
location_history_test
kate_view_mgmt_tests
bytearraysplitter_tests
)
/*
SPDX-FileCopyrightText: 2022 Waqar Ahmed <waqar.17a@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "bytearraysplitter_tests.h"
#include "bytearraysplitter.h"
#include <QTest>
#include <iostream>
QTEST_MAIN(ByteArraySplitterTests)
void ByteArraySplitterTests::test_data()
{
const QByteArray b("hello\0world\0waqar", sizeof("hello\0world\0waqar") - 1);
QTest::addColumn<QByteArray>("data");
QTest::addColumn<char>("splitOn");
QTest::addRow("1") << QByteArray("hello\0world\0foo", sizeof("hello\0world\0foo") - 1) << char('\0');
QTest::addRow("2") << QByteArray("hello\nworld\nfoo") << char('\n');
QTest::addRow("3") << QByteArray("abc\ndef\nhij") << char('z');
}
void ByteArraySplitterTests::test()
{
QFETCH(QByteArray, data);
QFETCH(char, splitOn);
QByteArrayList actual;
for (auto sv : ByteArraySplitter(data, splitOn)) {
actual.append(QByteArray(sv.data(), sv.size()));
}
QCOMPARE(data.split(splitOn), actual);
}
/*
SPDX-FileCopyrightText: 2022 Waqar Ahmed <waqar.17a@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <QObject>
class KateApp;
class KateViewSpace;
class KateViewManager;
class ByteArraySplitterTests : public QObject
{
Q_OBJECT
public:
using QObject::QObject;
private Q_SLOTS:
void test_data();
void test();
};
/*
SPDX-FileCopyrightText: 2022 Waqar Ahmed <waqar.17a@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KATE_BYTE_ARRAY_SPLITTER
#define KATE_BYTE_ARRAY_SPLITTER
#include <QByteArray>
#include <QString>
#include <charconv>
#include <optional>
#include <string_view>
class strview : public std::string_view
{
public:
using std::string_view::string_view;
strview(std::string_view s)
: std::string_view(s)
{
}
QString toString() const
{
return QString::fromUtf8(data(), size());
}
QByteArray toByteArray() const
{
return QByteArray(data(), size());
}
template<typename Int>
std::optional<Int> to(Int &&value = {}) const
{
auto res = std::from_chars(data(), data() + size(), value);
if (res.ptr == (data() + size())) {
return value;
}
return std::nullopt;
}
};
template<typename ArrayType>
class ByteArraySplitter
{
public:
ByteArraySplitter(const ArrayType &b, char splitChar)
: data(b)
, s(splitChar)
{
}
class iterator
{
public:
iterator()
{
}
iterator(const char *c, size_t size, char split)
: sv(c, size)
, s(size == 0 ? std::string::npos : 0)
, sp(split)
{
e = sv.find(sp, s);
}
iterator &operator++()
{
auto newPos = sv.find(sp, e == std::string::npos ? e : e + 1);
s = e != std::string::npos ? e + 1 : std::string::npos;
e = newPos;
return *this;
}
iterator operator++(int)
{
auto old = *this;
auto newPos = sv.find(sp, e == std::string::npos ? e : e + 1);
s = e != std::string::npos ? e + 1 : std::string::npos;
e = newPos;
return old;
}
strview operator*() const
{
if (s != std::string::npos) {
return sv.substr(s, e != std::string::npos ? (e - s) : e);
}
return std::string_view(nullptr, 0);
}
bool operator==(const iterator &r) const
{
return s == r.s && e == r.e && sp == r.sp;
}
bool operator!=(const iterator &r) const
{
return !(*this == r);
}
private:
strview sv;
std::size_t s;
std::size_t e;
char sp;
};
iterator begin() const
{
return iterator(data.data(), data.size(), s);
}
iterator end() const
{
return iterator(nullptr, 0, s);
}
bool empty() const
{
return begin() == end();
}
template<typename ContainerType>
ContainerType toContainer(ContainerType &&c = {})
{
for (auto e : *this)
c.push_back(e);
return std::forward<ContainerType>(c);
}
private:
const ArrayType &data;
char s;
};
#endif
......@@ -7,6 +7,7 @@
#include "filehistorywidget.h"
#include "diffparams.h"
#include "ktexteditor_utils.h"
#include <bytearraysplitter.h>
#include <gitprocess.h>
#include <QDate>
......@@ -26,6 +27,8 @@
#include <KTextEditor/Editor>
#include <KTextEditor/MainWindow>
#include <charconv>
struct Commit {
QByteArray hash;
QString authorName;
......@@ -38,52 +41,45 @@ struct Commit {
};
Q_DECLARE_METATYPE(Commit)
static std::optional<qint64> toLong(const QByteArray &input)
{
bool ok = false;
qint64 ret = input.toLongLong(&ok);
if (ok) {
return ret;
}
return std::nullopt;
}
static QVector<Commit> parseCommits(const QList<QByteArray> &raw)
static QVector<Commit> parseCommits(const QByteArray raw)
{
QVector<Commit> commits;
commits.reserve(raw.size());
for (int i = 0; i < raw.size(); ++i) {
const auto &commitDetails = raw.at(i);
if (commitDetails.isEmpty()) {
const auto splitted = ByteArraySplitter(raw, '\0');
for (auto it = splitted.begin(); it != splitted.end(); ++it) {
const auto commitDetails = *it;
if (commitDetails.empty()) {
continue;
}
const auto lines = commitDetails.split('\n');
const auto lines = ByteArraySplitter(commitDetails, '\n').toContainer<QVarLengthArray<strview, 7>>();
if (lines.length() < 7) {
continue;
}
QByteArray hash = lines.at(0);
QString author = QString::fromUtf8(lines.at(1));
QString email = QString::fromUtf8(lines.at(2));
auto authDate = toLong(lines.at(3));
QByteArray hash = lines.at(0).toByteArray();
QString author = lines.at(1).toString();
QString email = lines.at(2).toString();
auto authDate = lines.at(3).to<qint64>();
if (!authDate.has_value()) {
continue;
}
qint64 authorDate = authDate.value();
auto commtDate = toLong(lines.at(4));
auto commtDate = lines.at(4).to<qint64>();
if (!commtDate.has_value()) {
continue;
}
qint64 commitDate = commtDate.value();
QByteArray parent = lines.at(5);
QString msg = QString::fromUtf8(lines.at(6));
QByteArray parent = lines.at(5).toByteArray();
QString msg = lines.at(6).toString();
QByteArray file;
if (i + 1 < raw.size()) {
file = raw.at(i + 1).trimmed();
++it;
if (it != splitted.end()) {
file = (*it).toByteArray().trimmed();
}
commits << Commit{hash, author, email, authorDate, commitDate, parent, msg, file};
......@@ -292,7 +288,7 @@ void FileHistoryWidget::getFileHistory(const QString &file)
}
connect(&m_git, &QProcess::readyReadStandardOutput, this, [this] {
auto commits = parseCommits(m_git.readAllStandardOutput().split(0x00));
auto commits = parseCommits(m_git.readAllStandardOutput());
if (!commits.isEmpty()) {
static_cast<CommitListModel *>(m_listView->model())->addCommits(commits);
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment