feat(version): single-source semver, ext-state stamp, show-version action
Version "0.9.01" sourced once from CMake via configure_file'd header; pure app_version module (parse/compare/classify) with CTest. Stamp rides the persist save seam under "reasampler"/"version"; absent stamp reads as pre-versioning. On-demand "show version" action, no startup print.
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
// app_version.cpp — implementation of the pure version-identity core (Phase V, V1).
|
||||
// See app_version.h for the contract. The version STRING itself comes from
|
||||
// version_generated.h (produced by CMake configure_file from the one REASAMPLER_VERSION
|
||||
// variable) — this TU just re-exports it and owns the pure parse/compare/classify logic.
|
||||
|
||||
#include "app_version.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "version_generated.h" // REASAMPLER_VERSION_STRING — configure_file'd from CMake
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
const std::string& appVersion() {
|
||||
// Function-local static: initialized once from the compile-time string, returned by
|
||||
// const ref so callers share the one authoritative instance. The macro is the exact
|
||||
// CMake value, leading zero and all.
|
||||
static const std::string kVersion = REASAMPLER_VERSION_STRING;
|
||||
return kVersion;
|
||||
}
|
||||
|
||||
std::optional<Version> parseVersion(const std::string& s) {
|
||||
// Split on '.' into exactly three non-empty all-digit components. No sign, no
|
||||
// whitespace, no trailing garbage. Leading zeros are allowed (0.9.01 parses to
|
||||
// {0,9,1}) — the display fidelity of the zero is the string's job, not this parse's.
|
||||
Version v;
|
||||
int component = 0; // 0=major, 1=minor, 2=patch
|
||||
long long acc = 0; // current component accumulator (long long guards overflow)
|
||||
bool digitsInComponent = false;
|
||||
int* const out[3] = {&v.major, &v.minor, &v.patch};
|
||||
|
||||
for (char c : s) {
|
||||
if (c == '.') {
|
||||
if (!digitsInComponent) return std::nullopt; // empty component (".", "1..2")
|
||||
if (component >= 2) return std::nullopt; // too many dots
|
||||
*out[component] = static_cast<int>(acc);
|
||||
++component;
|
||||
acc = 0;
|
||||
digitsInComponent = false;
|
||||
continue;
|
||||
}
|
||||
if (c < '0' || c > '9') return std::nullopt; // non-digit (sign, letter, ws)
|
||||
acc = acc * 10 + (c - '0');
|
||||
if (acc > 1'000'000'000LL) return std::nullopt; // absurdly large -> reject
|
||||
digitsInComponent = true;
|
||||
}
|
||||
if (component != 2 || !digitsInComponent) return std::nullopt; // too few components
|
||||
*out[2] = static_cast<int>(acc);
|
||||
return v;
|
||||
}
|
||||
|
||||
bool versionLess(const Version& a, const Version& b) {
|
||||
if (a.major != b.major) return a.major < b.major;
|
||||
if (a.minor != b.minor) return a.minor < b.minor;
|
||||
return a.patch < b.patch;
|
||||
}
|
||||
|
||||
WritingVersion classifyWritingVersion(const std::string& rawStamp) {
|
||||
WritingVersion wv;
|
||||
if (rawStamp.empty()) {
|
||||
wv.kind = WritingVersion::Kind::PreVersioning; // no stamp -> pre-versioning
|
||||
return wv;
|
||||
}
|
||||
std::optional<Version> parsed = parseVersion(rawStamp);
|
||||
if (!parsed) {
|
||||
wv.kind = WritingVersion::Kind::Unknown; // present but malformed -> ignored
|
||||
wv.raw = rawStamp;
|
||||
return wv;
|
||||
}
|
||||
wv.kind = WritingVersion::Kind::Stamped;
|
||||
wv.raw = rawStamp;
|
||||
wv.parsed = *parsed;
|
||||
return wv;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
Reference in New Issue
Block a user