// Copyright (C) 2025 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "filecache.h" #include #include #include #include #include using namespace Qt::StringLiterals; constexpr qsizetype MAX_CACHE_SIZE = 20; static QString msgCannotFindSnippet(const QString &file, const QString &snippetLabel) { return "Cannot find snippet \""_L1 + snippetLabel + "\" in \""_L1 + QDir::toNativeSeparators(file) + "\"."_L1; } static QString msgUnterminatedSnippet(const QString &file, const QString &snippetLabel) { return "Snippet \""_L1 + snippetLabel + "\" in \""_L1 + QDir::toNativeSeparators(file) + "\" is not terminated."_L1; } static QString msgCannotOpenFileForReading(const QFile &f) { return "Failed to open file \""_L1 + QDir::toNativeSeparators(f.fileName()) + "\" for reading: "_L1 +f.errorString(); } std::optional FileCache::fileContents(const QString &name) { const qsizetype index = ensureEntry(name); if (index == -1) return std::nullopt; return m_cache.at(index).contents; } void FileCache::ensureLines(FileCacheEntry *entry) { if (entry->lines.isEmpty()) entry->lines = QStringView{entry->contents}.split(u'\n'); } std::optional FileCache::lines(const QString &name) { const qsizetype index = ensureEntry(name); if (index == -1) return std::nullopt; FileCacheEntry &entry = m_cache[index]; ensureLines(&entry); return entry.lines; } std::optional FileCache::fileSnippet(const QString &name, const QString &snippetName, const QRegularExpression &snippetPattern) { const qsizetype index = ensureEntry(name); if (index == -1) return std::nullopt; FileCacheEntry &entry = m_cache[index]; ensureLines(&entry); // Check for a comment line and the snippet ID auto pred = [&snippetPattern](QStringView line) { return (line.contains(u'/') || line.contains(u'#')) && snippetPattern.matchView(line).hasMatch(); }; const auto end = entry.lines.cend(); const auto i1 = std::find_if(entry.lines.cbegin(), end, pred); if (i1 == end) { m_error = msgCannotFindSnippet(name, snippetName); return std::nullopt; } auto pos = i1; const auto i2 = std::find_if(++pos, end, pred); if (i2 == end) { m_error = msgUnterminatedSnippet(name, snippetName); return std::nullopt; } const QChar *startSnippet = i1->constData() + i1->size() + 1; const auto snippetSize = i2->constData() - startSnippet; const auto startSnippetIndex = startSnippet - entry.lines.cbegin()->constData(); return entry.contents.sliced(startSnippetIndex, snippetSize); } qsizetype FileCache::ensureEntry(const QString &name) { const qsizetype index = indexOf(name); if (index != -1) { ++m_hits; return index; } ++m_misses; m_error.clear(); QFile file(name); if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) { m_error = msgCannotOpenFileForReading(file); return -1; } QString contents = QString::fromUtf8(file.readAll()); m_cache.prepend({name, contents, {}}); while (m_cache.size() >= MAX_CACHE_SIZE) m_cache.removeLast(); return 0; } qsizetype FileCache::indexOf(const QString &name) const { for (qsizetype i = 0, size = m_cache.size(); i < size; ++i) { if (m_cache.at(i).name == name) return i; } return -1; } void FileCache::formatDebug(QDebug &debug) const { debug << "FileCache(" << m_cache.size() << " entries, " << m_hits << " hits, " << m_misses << " misses ["; for (const auto &e : m_cache) debug << QDir::toNativeSeparators(e.name) << ' ' << e.contents.size() << "B "; debug << "])"; } QDebug operator<<(QDebug debug, const FileCache &c) { QDebugStateSaver saver(debug); debug.noquote(); debug.nospace(); c.formatDebug(debug); return debug; }