Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/tools/Versions.cpp
diff options
context:
space:
mode:
authorLeitwolf2026-11-02 00:51:11 +0300
committerGitHub2026-11-02 00:51:11 +0300
commit268698be92962247a21a86dda4de2d4267adcf8f (patch)
tree510f7ece1750b2da6d9b4666b7c58908c8d193bb /tools/Versions.cpp
parent13324a043042110eba456424fa39c1334857ceaa (diff)
parent3ed0fd930371823b6d44ae64d00dbd79e421a552 (diff)
downloadcatch3-268698be92962247a21a86dda4de2d4267adcf8f.tar.gz
Merge pull request #36 from Progmasoft/feature/cpp-tools
Move the tools from Python to C++17 and Bazel
Diffstat (limited to 'tools/Versions.cpp')
-rw-r--r--tools/Versions.cpp233
1 files changed, 233 insertions, 0 deletions
diff --git a/tools/Versions.cpp b/tools/Versions.cpp
new file mode 100644
index 00000000..abc6d5b3
--- /dev/null
+++ b/tools/Versions.cpp
@@ -0,0 +1,233 @@
+// Copyright (c) 2026 Progmasoft.
+// SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0
+
+#include <algorithm>
+#include <cctype>
+#include <charconv>
+#include <utility>
+#include <vector>
+
+#include "tools/Versions.hpp"
+
+namespace Progmasoft::Catch3::Tools
+{
+ namespace
+ {
+ constexpr std::string_view kVersionHeader
+ = "src/Progmasoft/Catch3/Version.hpp";
+ } // namespace
+
+ std::optional<Version>
+ ParseVersion(std::string_view text)
+ {
+ Version version{};
+ std::size_t count = 0;
+ while (true)
+ {
+ const auto dot = text.find('.');
+ const auto number = text.substr(0, dot);
+ if (count == version.size() || number.empty()
+ || (number.size() > 1 && number.front() == '0'))
+ {
+ return std::nullopt;
+ }
+ const auto *end = number.data() + number.size();
+ const auto parsed
+ = std::from_chars(number.data(), end, version[count]);
+ if (parsed.ec != std::errc{} || parsed.ptr != end)
+ {
+ return std::nullopt;
+ }
+ ++count;
+ if (dot == std::string_view::npos)
+ {
+ break;
+ }
+ text.remove_prefix(dot + 1);
+ }
+ if (count < 3)
+ {
+ return std::nullopt;
+ }
+ return version;
+ }
+
+ std::string
+ FormatVersion(const Version &version)
+ {
+ return std::to_string(version[0]) + "." + std::to_string(version[1])
+ + "." + std::to_string(version[2]) + "."
+ + std::to_string(version[3]);
+ }
+
+ Version
+ NextVersion(Version version)
+ {
+ if (version[3] < 9)
+ {
+ ++version[3];
+ return version;
+ }
+ version[3] = 0;
+ if (version[2] < 9)
+ {
+ ++version[2];
+ return version;
+ }
+ version[2] = 0;
+ if (version[1] < 19)
+ {
+ ++version[1];
+ return version;
+ }
+ version[1] = 0;
+ ++version[0];
+ return version;
+ }
+
+ const std::array<VersionSite, 5> &
+ VersionSites()
+ {
+ static constexpr std::array<VersionSite, 5> kSites{ {
+ { "CMakeLists.txt", "\n VERSION ", "\n" },
+ { "MODULE.bazel", "module(name = \"catch3\", version = \"", "\"" },
+ { "meson.build", "\n version: '", "'" },
+ { "xmake.lua", "\nset_version(\"", "\"" },
+ { kVersionHeader, "kVersion = \"", "\"" },
+ } };
+ return kSites;
+ }
+
+ std::optional<std::string>
+ ReadVersionSite(std::string_view text, const VersionSite &site)
+ {
+ const auto before = text.find(site.Before);
+ if (before == std::string_view::npos)
+ {
+ return std::nullopt;
+ }
+ const auto first = before + site.Before.size();
+ const auto end = text.find(site.After, first);
+ if (end == std::string_view::npos)
+ {
+ return std::nullopt;
+ }
+ return std::string{ text.substr(first, end - first) };
+ }
+
+ std::optional<std::string>
+ WriteVersionSite(std::string_view text,
+ const VersionSite &site,
+ std::string_view version)
+ {
+ const auto current = ReadVersionSite(text, site);
+ if (!current)
+ {
+ return std::nullopt;
+ }
+ const auto first = text.find(site.Before) + site.Before.size();
+ std::string result{ text.substr(0, first) };
+ result += version;
+ result += text.substr(first + current->size());
+ return result;
+ }
+
+ std::optional<std::string>
+ CurrentVersion(const std::filesystem::path &root, Problems &problems)
+ {
+ const auto &sites = VersionSites();
+ const auto &site
+ = *std::find_if(sites.begin(),
+ sites.end(),
+ [](const VersionSite &candidate) {
+ return candidate.File == kVersionHeader;
+ });
+ auto version
+ = ReadVersionSite(ReadOrReport(root / site.File, problems), site);
+ if (!version)
+ {
+ problems.emplace_back("no version in Version.hpp");
+ }
+ return version;
+ }
+
+ void
+ CheckVersions(const std::filesystem::path &root, Problems &problems)
+ {
+ const auto current = CurrentVersion(root, problems);
+ if (!current)
+ {
+ return;
+ }
+ for (const auto &site : VersionSites())
+ {
+ const auto version
+ = ReadVersionSite(ReadOrReport(root / site.File, problems),
+ site);
+ if (version != current)
+ {
+ problems.push_back(std::string{ site.File } + " states "
+ + version.value_or("no version")
+ + ", Version.hpp states " + *current);
+ }
+ }
+ }
+
+ void
+ SetVersion(const std::filesystem::path &root,
+ const std::string &version,
+ Problems &problems)
+ {
+ std::vector<std::pair<std::filesystem::path, std::string>> writes;
+ for (const auto &site : VersionSites())
+ {
+ const auto path = root / site.File;
+ auto text
+ = WriteVersionSite(ReadOrReport(path, problems), site, version);
+ if (text)
+ {
+ writes.emplace_back(path, *std::move(text));
+ }
+ else
+ {
+ problems.push_back("cannot set the version in "
+ + std::string{ site.File });
+ }
+ }
+ // All of them or none, so that the places never disagree.
+ if (!problems.empty())
+ {
+ return;
+ }
+ for (const auto &[path, text] : writes)
+ {
+ if (!WriteText(path, text))
+ {
+ problems.push_back("cannot write " + path.generic_string());
+ }
+ }
+ }
+
+ std::optional<std::string>
+ ReleaseNotesOf(std::string_view notes, std::string_view version)
+ {
+ auto heading = "\n## " + std::string{ version } + "\n";
+ auto found = notes.find(heading);
+ // A release with D = 0 may be headed by its three numbers.
+ const auto parsed = ParseVersion(version);
+ if (found == std::string_view::npos && parsed && (*parsed)[3] == 0)
+ {
+ heading = "\n## " + std::to_string((*parsed)[0]) + "."
+ + std::to_string((*parsed)[1]) + "."
+ + std::to_string((*parsed)[2]) + "\n";
+ found = notes.find(heading);
+ }
+ if (found == std::string_view::npos)
+ {
+ return std::nullopt;
+ }
+ auto section = notes.substr(found + heading.size());
+ section = section.substr(0, section.find("\n## "));
+ return std::string{ Trimmed(section) } + "\n";
+ }
+} // namespace Progmasoft::Catch3::Tools