aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2018-10-18 07:05:43 +0200
committerdec05eba <dec05eba@protonmail.com>2020-07-06 07:39:33 +0200
commit6bb79ef033c2a2e8f12c9da6409e3547af40417c (patch)
tree6fe6c5bea99cc0b38af92a7aa4714e5614de4868 /src
parent4e38f2af2b97850ec5b395d4e0ea8310e664e52f (diff)
Use ranges for dependency version
Diffstat (limited to 'src')
-rw-r--r--src/CmakeModule.cpp1
-rw-r--r--src/Conf.cpp23
-rw-r--r--src/FileUtil.cpp27
-rw-r--r--src/GlobalLib.cpp40
-rw-r--r--src/Package.cpp65
-rw-r--r--src/PkgConfig.cpp48
-rw-r--r--src/Version.cpp86
-rw-r--r--src/VersionParser.cpp362
-rw-r--r--src/main.cpp2
9 files changed, 588 insertions, 66 deletions
diff --git a/src/CmakeModule.cpp b/src/CmakeModule.cpp
index 1e873cd..91c572d 100644
--- a/src/CmakeModule.cpp
+++ b/src/CmakeModule.cpp
@@ -239,6 +239,7 @@ namespace sibs
}
}
}
+ return true;
});
// TODO: Clean this up. The below code is indentical to code in Ninja.cpp.......
diff --git a/src/Conf.cpp b/src/Conf.cpp
index a204f13..7ae589e 100644
--- a/src/Conf.cpp
+++ b/src/Conf.cpp
@@ -1,5 +1,6 @@
#include "../include/Conf.hpp"
#include "../include/types.hpp"
+#include "../include/VersionParser.hpp"
#include "../external/utf8/unchecked.h"
#include <iostream>
@@ -482,7 +483,7 @@ namespace sibs
return Result<bool>::Err(errMsg);
}
- if(config.version.empty())
+ if(config.versionStr.empty())
{
string errMsg = "The project ";
errMsg += config.getPackageName();
@@ -670,6 +671,7 @@ namespace sibs
{
if(_tinydir_strcmp(file->extension, CONFIG_STATIC_LIB_FILE_EXTENSION) == 0)
outputFiles.push_back(toUtf8(file->path));
+ return true;
});
}
@@ -747,12 +749,17 @@ namespace sibs
else if(name.equals("version"))
{
if (value.isSingle())
- version = string(value.asSingle().data, value.asSingle().size);
+ versionStr = string(value.asSingle().data, value.asSingle().size);
else
throw ParserException("Expected package.version to be a single value, was a list");
- if(!isVersionStringValid(version))
- throw ParserException("package.version is in invalid format. Version string can only contain numbers and dots");
+ int versionSize = 0;
+ Result<PackageVersion> versionResult = parsePackageVersion(value.asSingle(), &versionSize);
+ if(!versionResult)
+ throw ParserException("package.version is in invalid format, error: " + versionResult.getErrMsg());
+ if(versionSize != (int)versionStr.size())
+ throw ParserException("package.version is in invalid format, expected to only contain numbers and dots");
+ version = versionResult.unwrap();
}
else if(name.equals("authors"))
{
@@ -1025,10 +1032,14 @@ namespace sibs
{
if(value.isSingle())
{
- // TODO: Validate version is number in correct format
+ VersionParser versionParser;
+ Result<PackageVersionRange> dependencyVersionResult = versionParser.parse(value.asSingle().data, value.asSingle().size);
+ if(!dependencyVersionResult)
+ throw ParserException("Dependency " + string(name.data, name.size) + " version is in invalid format, error: " + dependencyVersionResult.getErrMsg());
+
PackageListDependency *dependency = new PackageListDependency();
dependency->name = string(name.data, name.size);
- dependency->version = string(value.asSingle().data, value.asSingle().size);
+ dependency->version = dependencyVersionResult.unwrap();
packageListDependencies.emplace_back(dependency);
}
else if(value.isObject())
diff --git a/src/FileUtil.cpp b/src/FileUtil.cpp
index b41808b..d33c94d 100644
--- a/src/FileUtil.cpp
+++ b/src/FileUtil.cpp
@@ -175,7 +175,11 @@ namespace sibs
tinydir_file file;
tinydir_readfile(&dir, &file);
if(_tinydir_strncmp(file.name, TINYDIR_STRING("."), 1) != 0)
- callbackFunc(&file);
+ {
+ bool doContinue = callbackFunc(&file);
+ if(!doContinue)
+ break;
+ }
tinydir_next(&dir);
}
@@ -193,7 +197,11 @@ namespace sibs
tinydir_file file;
tinydir_readfile(&dir, &file);
if(file.is_reg)
- callbackFunc(&file);
+ {
+ bool doContinue = callbackFunc(&file);
+ if(!doContinue)
+ break;
+ }
tinydir_next(&dir);
}
@@ -201,7 +209,7 @@ namespace sibs
}
// TODO: Handle failure (directory doesn't exist, no permission etc)
- void walkDirFilesRecursive(const _tinydir_char_t *directory, FileWalkCallbackFunc callbackFunc)
+ bool walkDirFilesRecursive(const _tinydir_char_t *directory, FileWalkCallbackFunc callbackFunc)
{
tinydir_dir dir;
tinydir_open(&dir, directory);
@@ -211,13 +219,22 @@ namespace sibs
tinydir_file file;
tinydir_readfile(&dir, &file);
if(file.is_reg)
- callbackFunc(&file);
+ {
+ bool doContinue = callbackFunc(&file);
+ if(!doContinue)
+ return false;
+ }
else if(_tinydir_strncmp(file.name, TINYDIR_STRING("."), 1) != 0)
- walkDirFilesRecursive(file.path, callbackFunc);
+ {
+ bool doContinue = walkDirFilesRecursive(file.path, callbackFunc);
+ if(!doContinue)
+ return false;
+ }
tinydir_next(&dir);
}
tinydir_close(&dir);
+ return true;
}
Result<StringView> getFileContent(const _tinydir_char_t *filepath)
diff --git a/src/GlobalLib.cpp b/src/GlobalLib.cpp
index 242e621..61e6648 100644
--- a/src/GlobalLib.cpp
+++ b/src/GlobalLib.cpp
@@ -8,6 +8,7 @@
#include "../include/CmakeModule.hpp"
#include "../include/Dependency.hpp"
#include "../include/GitRepository.hpp"
+#include "../include/VersionParser.hpp"
using namespace std;
@@ -69,7 +70,7 @@ namespace sibs
// TODO: If return error is invalid url, then the message should be converted to
// invalid package name/version. A check should be done if it is the name or version
// that is invalid.
- Result<bool> downloadDependencyResult = GlobalLib::downloadDependency(globalLibDependency);
+ Result<bool> downloadDependencyResult = GlobalLib::downloadDependency(globalLibDependency, parentConfig.platform);
if(!downloadDependencyResult)
return downloadDependencyResult;
@@ -118,7 +119,7 @@ namespace sibs
return Result<bool>::Ok(true);
}
- Result<bool> GlobalLib::getLibsLinkerFlags(const SibsConfig &parentConfig, const FileString &globalLibRootDir, const std::string &name, const std::string &version, LinkerFlagCallbackFunc staticLinkerFlagCallbackFunc, LinkerFlagCallbackFunc dynamicLinkerFlagCallbackFunc, GlobalIncludeDirCallbackFunc globalIncludeDirCallback)
+ Result<bool> GlobalLib::getLibsLinkerFlags(const SibsConfig &parentConfig, const FileString &globalLibRootDir, const std::string &name, const PackageVersionRange &versionRange, LinkerFlagCallbackFunc staticLinkerFlagCallbackFunc, LinkerFlagCallbackFunc dynamicLinkerFlagCallbackFunc, GlobalIncludeDirCallbackFunc globalIncludeDirCallback)
{
Result<bool> packageExistsResult = validatePackageExists(globalLibRootDir, name);
if (packageExistsResult.isErr())
@@ -126,33 +127,34 @@ namespace sibs
#if OS_FAMILY == OS_FAMILY_POSIX
FileString namePlatformNative = name;
- FileString versionPlatformNative = version;
#else
FileString namePlatformNative = utf8To16(name);
- FileString versionPlatformNative = utf8To16(version);
#endif
FileString packageDir = globalLibRootDir + TINYDIR_STRING("/");
packageDir += namePlatformNative;
- // TODO: Instead of checking if version is exact match, check if package has same major version
- // and same or newer minor version
FileString foundVersion;
- walkDir(packageDir.c_str(), [&foundVersion, &versionPlatformNative](tinydir_file *file)
+ walkDir(packageDir.c_str(), [&foundVersion, &versionRange](tinydir_file *file)
{
if(file->is_dir)
{
- //printf("version: %s\n", file->name);
- if(_tinydir_strcmp(versionPlatformNative.c_str(), file->name) == 0)
+ string versionUtf8 = toUtf8(file->name);
+ Result<PackageVersion> versionResult = parsePackageVersion({ versionUtf8.data(), versionUtf8.size() }, nullptr);
+ if(versionResult && versionRange.isInRange(versionResult.unwrap()))
+ {
foundVersion = file->name;
+ return false;
+ }
}
+ return true;
});
if(foundVersion.empty())
- return Result<bool>::Err("Global lib dependency found, but version doesn't match dependency version", DependencyError::DEPENDENCY_VERSION_NO_MATCH);
+ return Result<bool>::Err("Global lib dependency found, but version isn't in range of version", DependencyError::DEPENDENCY_VERSION_NO_MATCH);
packageDir += TINYDIR_STRING("/");
- packageDir += versionPlatformNative;
+ packageDir += foundVersion;
return GlobalLib::getLibsLinkerFlagsCommon(parentConfig, packageDir, name, staticLinkerFlagCallbackFunc, dynamicLinkerFlagCallbackFunc, globalIncludeDirCallback);
}
@@ -261,13 +263,13 @@ namespace sibs
}
}
- Result<bool> GlobalLib::downloadDependency(PackageListDependency *dependency)
+ Result<bool> GlobalLib::downloadDependency(PackageListDependency *dependency, Platform platform)
{
- Result<string> packageUrlResult = Package::getPackageUrl(dependency->name.c_str(), dependency->version.c_str(), SYSTEM_PLATFORM);
- if(!packageUrlResult)
- return Result<bool>::Err(packageUrlResult);
+ Result<PackageMetadata> packageResult = Package::getPackage(dependency->name.c_str(), dependency->version, platform);
+ if(!packageResult)
+ return Result<bool>::Err(packageResult);
- const string &url = packageUrlResult.unwrap();
+ const PackageMetadata &package = packageResult.unwrap();
Result<FileString> libPathResult = getHomeDir();
if (!libPathResult)
@@ -276,7 +278,7 @@ namespace sibs
libPath += TINYDIR_STRING("/.cache/sibs/lib/");
libPath += toFileString(dependency->name);
libPath += TINYDIR_STRING("/");
- libPath += toFileString(dependency->version);
+ libPath += package.version.toString();
FileString libArchivedFilePath = libPathResult.unwrap();
libArchivedFilePath += TINYDIR_STRING("/.cache/sibs/archive/");
@@ -286,8 +288,8 @@ namespace sibs
return createArchiveDirResult;
libArchivedFilePath += TINYDIR_STRING("/");
- libArchivedFilePath += toFileString(dependency->version);
- Result<bool> downloadResult = curl::downloadFile(url.c_str(), libArchivedFilePath.c_str());
+ libArchivedFilePath += package.version.toString();
+ Result<bool> downloadResult = curl::downloadFile(package.urls[0].c_str(), libArchivedFilePath.c_str());
if(!downloadResult)
return downloadResult;
diff --git a/src/Package.cpp b/src/Package.cpp
index eab0680..925de82 100644
--- a/src/Package.cpp
+++ b/src/Package.cpp
@@ -1,5 +1,6 @@
#include "../include/Package.hpp"
#include "../include/curl.hpp"
+#include "../include/VersionParser.hpp"
#include "../external/rapidjson/error/en.h"
#ifdef GetObject
@@ -37,7 +38,15 @@ namespace sibs
PackageMetadata packageMetadata;
packageMetadata.description.assign(description->value.GetString(), description->value.GetStringLength());
- packageMetadata.version.assign(version->value.GetString(), version->value.GetStringLength());
+
+ int versionStrSize = 0;
+ Result<PackageVersion> versionResult = parsePackageVersion({ version->value.GetString(), version->value.GetStringLength() }, &versionStrSize);
+ if(!versionResult)
+ return Result<PackageMetadata>::Err("package version is in wrong fromat, error: " + versionResult.getErrMsg());
+ if(versionStrSize != (int)version->value.GetStringLength())
+ return Result<PackageMetadata>::Err("package version is in wrong format, expected only numbers and dots (version: " + string(version->value.GetString(), version->value.GetStringLength()) + ")");
+
+ packageMetadata.version = versionResult.unwrap();
const auto &platformsArray = platforms->value.GetArray();
packageMetadata.platforms.reserve(platformsArray.Size());
@@ -50,6 +59,9 @@ namespace sibs
}
const auto &urlsArray = urls->value.GetArray();
+ if(urlsArray.Empty())
+ return Result<PackageMetadata>::Err("Expected url list to not be empty");
+
packageMetadata.urls.reserve(urlsArray.Size());
for(int i = 0; i < urlsArray.Size(); ++i)
{
@@ -62,16 +74,16 @@ namespace sibs
return Result<PackageMetadata>::Ok(packageMetadata);
}
- static Result<string> getPackageUrl(const PackageMetadata &packageMetadata, const char *packageName, const char *packageVersion, Platform platform)
+ static Result<bool> isPackageUsableForPlatform(const PackageMetadata &packageMetadata, const char *packageName, const PackageVersionRange &versionRange, Platform platform)
{
- if(strcmp(packageMetadata.version.c_str(), packageVersion) != 0)
+ if(!versionRange.isInRange(packageMetadata.version))
{
string errMsg = "Package \"";
errMsg += packageName;
- errMsg += "\" does not exist for version \"";
- errMsg += packageVersion;
+ errMsg += "\" does not exist for version range \"";
+ errMsg += versionRange.toString();
errMsg += "\"";
- return Result<string>::Err(errMsg);
+ return Result<bool>::Err(errMsg);
}
if(!containsPlatform(getPlatformsByNames(packageMetadata.platforms), platform))
@@ -79,14 +91,14 @@ namespace sibs
string errMsg = "Package \"";
errMsg += packageName;
errMsg += "\" with version \"";
- errMsg += packageVersion;
+ errMsg += packageMetadata.version.toString();
errMsg += "\" does not support platform \"";
errMsg += platform;
errMsg += "\"";
- return Result<string>::Err(errMsg);
+ return Result<bool>::Err(errMsg);
}
-
- return Result<string>::Ok(packageMetadata.urls[0]);
+
+ return Result<bool>::Ok(true);
}
// TODO: Always downloading is fine right now because the package list is small. This should later be modified to use local cache.
@@ -120,11 +132,11 @@ namespace sibs
return Result<Document*>::Ok(packageList);
}
- Result<string> Package::getPackageUrl(const char *packageName, const char *packageVersion, Platform platform)
+ Result<PackageMetadata> Package::getPackage(const char *packageName, const PackageVersionRange &versionRange, Platform platform)
{
Result<Document*> packageList = Package::getPackageList("https://gitlab.com/DEC05EBA/sibs_packages/raw/master/packages.json");
if(!packageList)
- return Result<string>::Err(packageList);
+ return Result<PackageMetadata>::Err(packageList);
const Document &packageDoc = *packageList.unwrap();
@@ -134,14 +146,18 @@ namespace sibs
string errMsg = "No package with the name \"";
errMsg += packageName;
errMsg += "\" was found";
- return Result<string>::Err(errMsg);
+ return Result<PackageMetadata>::Err(errMsg);
}
if(packageMetaDataJsonIt->value.IsObject())
{
Result<PackageMetadata> packageMetadataResult = getPackageMetadata(packageMetaDataJsonIt->value.GetObject());
- if(!packageMetadataResult) return Result<string>::Err(packageMetadataResult);
- return ::sibs::getPackageUrl(packageMetadataResult.unwrap(), packageName, packageVersion, platform);
+ if(!packageMetadataResult) return packageMetadataResult;
+ Result<bool> packageUsableResult = isPackageUsableForPlatform(packageMetadataResult.unwrap(), packageName, versionRange, platform);
+ if(packageUsableResult)
+ return Result<PackageMetadata>::Ok(packageMetadataResult.unwrap());
+ else
+ return Result<PackageMetadata>::Err(packageUsableResult);
}
else if(packageMetaDataJsonIt->value.IsArray())
{
@@ -155,35 +171,32 @@ namespace sibs
errMsg += "[";
errMsg += to_string(i);
errMsg += "] is not an object";
- return Result<string>::Err(errMsg);
+ return Result<PackageMetadata>::Err(errMsg);
}
Result<PackageMetadata> packageMetadataResult = getPackageMetadata(packageData.GetObject());
- if(packageMetadataResult)
- {
- Result<string> packageUrlResult = ::sibs::getPackageUrl(packageMetadataResult.unwrap(), packageName, packageVersion, platform);
- if(packageUrlResult)
- return packageUrlResult;
- }
+ if(!packageMetadataResult) return packageMetadataResult;
+ if(isPackageUsableForPlatform(packageMetadataResult.unwrap(), packageName, versionRange, platform))
+ return Result<PackageMetadata>::Ok(packageMetadataResult.unwrap());
++i;
}
string errMsg = "Package \"";
errMsg += packageName;
- errMsg += "\" with version \"";
- errMsg += packageVersion;
+ errMsg += "\" in version range \"";
+ errMsg += versionRange.toString();
errMsg += "\" does not exist or does not exist for platform \"";
errMsg += asString(platform);
errMsg += "\"";
- return Result<string>::Err(errMsg);
+ return Result<PackageMetadata>::Err(errMsg);
}
else
{
string errMsg = "No package with the name \"";
errMsg += packageName;
errMsg += "\" was found";
- return Result<string>::Err(errMsg);
+ return Result<PackageMetadata>::Err(errMsg);
}
}
}
diff --git a/src/PkgConfig.cpp b/src/PkgConfig.cpp
index e8f742c..89d3a44 100644
--- a/src/PkgConfig.cpp
+++ b/src/PkgConfig.cpp
@@ -1,6 +1,7 @@
#include "../include/PkgConfig.hpp"
#include "../include/Exec.hpp"
#include "../include/Dependency.hpp"
+#include "../include/VersionParser.hpp"
using namespace std;
@@ -32,9 +33,12 @@ namespace sibs
if(dependencyValidationResult.isErr())
return Result<bool>::Err(dependencyValidationResult.getErrMsg());
- Result<bool> dependencyVersionValidationResult = PkgConfig::validatePackageVersionAtLeast(dependency->name, dependency->version);
- if(dependencyVersionValidationResult.isErr())
- return Result<bool>::Err(dependencyVersionValidationResult.getErrMsg());
+ Result<PackageVersion> dependencyVersionResult = PkgConfig::getPackageVersion(dependency->name);
+ if(!dependencyVersionResult)
+ return Result<bool>::Err(dependencyVersionResult);
+
+ if(!dependency->version.isInRange(dependencyVersionResult.unwrap()))
+ return Result<bool>::Err("pkg-config package " + dependency->name + " exists but the version does not match our expected version range");
return Result<bool>::Ok(true);
}
@@ -70,11 +74,6 @@ namespace sibs
Result<bool> PkgConfig::validatePackageVersionAtLeast(const string &name, const string &version)
{
- // TODO: Instead of checking if package version is same or newer, check if package is same major version
- // and same or newer minor version
-
- // Use --modversion instead and check if the version returned is newer or equal to dependency version.
- // This way we can output installed version vs expected dependency version
FileString command = pkgConfigPath + TINYDIR_STRING(" '--atleast-version=");
command += toFileString(version);
command += TINYDIR_STRING("' '");
@@ -98,7 +97,7 @@ namespace sibs
}
else if(execResult.unwrap().exitCode != 0)
{
- string errMsg = "Failed to check dependency version, Unknown error, exit code: ";
+ string errMsg = "Failed to check pkg-config package version, Unknown error, exit code: ";
errMsg += to_string(execResult.unwrap().exitCode);
return Result<bool>::Err(errMsg);
}
@@ -106,6 +105,37 @@ namespace sibs
return Result<bool>::Ok(true);
}
+ Result<PackageVersion> PkgConfig::getPackageVersion(const std::string &name)
+ {
+ FileString command = pkgConfigPath + TINYDIR_STRING(" --modversion '");
+ command += toFileString(name);
+ command += TINYDIR_STRING("'");
+ Result<ExecResult> execResult = exec(command.c_str());
+ if(!execResult)
+ return Result<PackageVersion>::Err(execResult.getErrMsg());
+
+ if(execResult.unwrap().exitCode == 1)
+ {
+ string errMsg = "Dependency ";
+ errMsg += name;
+ errMsg += " not found in pkg-config";
+ return Result<PackageVersion>::Err(errMsg);
+ }
+ else if(execResult.unwrap().exitCode == 127)
+ {
+ return Result<PackageVersion>::Err("pkg-config is not installed");
+ }
+ else if(execResult.unwrap().exitCode != 0)
+ {
+ string errMsg = "Failed to get pkg-config package version, Unknown error, exit code: ";
+ errMsg += to_string(execResult.unwrap().exitCode);
+ return Result<PackageVersion>::Err(errMsg);
+ }
+
+ // Intentionally allow packages which have a version that contains more data after patch number, since some pkg-config packages are not in semver format
+ return parsePackageVersion({ execResult.unwrap().execStdout.data(), execResult.unwrap().execStdout.size() }, nullptr);
+ }
+
Result<string> PkgConfig::getDynamicLibsLinkerFlags(const vector<PackageListDependency*> &libs)
{
if(libs.empty()) return Result<string>::Ok("");
diff --git a/src/Version.cpp b/src/Version.cpp
new file mode 100644
index 0000000..26e5dbd
--- /dev/null
+++ b/src/Version.cpp
@@ -0,0 +1,86 @@
+#include "../include/Version.hpp"
+#include <cassert>
+
+namespace sibs
+{
+ const char* asString(VersionOperation operation)
+ {
+ switch(operation)
+ {
+ case VersionOperation::LESS: return "<";
+ case VersionOperation::LESS_EQUAL: return "<=";
+ case VersionOperation::EQUAL: return "=";
+ case VersionOperation::GREATER: return ">";
+ case VersionOperation::GREATER_EQUAL: return ">=";
+ default: return "";
+ }
+ }
+
+ std::string PackageVersion::toString() const
+ {
+ std::string result;
+ result += std::to_string(major);
+ result += ".";
+ result += std::to_string(minor);
+ result += ".";
+ result += std::to_string(patch);
+ return result;
+ }
+
+ std::string PackageVersionRange::toString() const
+ {
+ std::string result;
+ result += asString(startOperation);
+ result += start.toString();
+ if(endDefined)
+ {
+ result += " and ";
+ result += asString(endOperation);
+ result += end.toString();
+ }
+ return result;
+ }
+
+ static bool isInRangeOfEnd(const PackageVersionRange &versionRange, const PackageVersion &version)
+ {
+ if(!versionRange.endDefined)
+ return true;
+
+ switch(versionRange.endOperation)
+ {
+ case VersionOperation::LESS:
+ return version < versionRange.end;
+ case VersionOperation::LESS_EQUAL:
+ return version <= versionRange.end;
+ default:
+ assert(false);
+ return true;
+ }
+ }
+
+ bool PackageVersionRange::isInRange(const PackageVersion &version) const
+ {
+ switch(startOperation)
+ {
+ case VersionOperation::LESS:
+ return version < start;
+ case VersionOperation::LESS_EQUAL:
+ return version <= start;
+ case VersionOperation::EQUAL:
+ return version == start;
+ case VersionOperation::GREATER:
+ {
+ if(version <= start)
+ return false;
+ return isInRangeOfEnd(*this, version);
+ }
+ case VersionOperation::GREATER_EQUAL:
+ {
+ if(version < start)
+ return false;
+ return isInRangeOfEnd(*this, version);
+ }
+ }
+ return true;
+ }
+} \ No newline at end of file
diff --git a/src/VersionParser.cpp b/src/VersionParser.cpp
new file mode 100644
index 0000000..7b28cef
--- /dev/null
+++ b/src/VersionParser.cpp
@@ -0,0 +1,362 @@
+#include "../include/VersionParser.hpp"
+#include "../include/StringView.hpp"
+
+namespace sibs
+{
+ static int stringToIntNoVerify(const StringView &str)
+ {
+ int result = 0;
+ if(str.size > 0)
+ result += (str[0] - '0');
+
+ for(int i = 1; i < (int)str.size; ++i)
+ {
+ int num = str[i] - '0';
+ result += (10 * ((int)str.size - i) * num);
+ }
+ return result;
+ }
+
+ static bool isNum(char c)
+ {
+ return c >= '0' && c <= '9';
+ }
+
+ static bool isAlpha(char c)
+ {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+ }
+
+ Result<PackageVersion> parsePackageVersion(const StringView &versionStr, int *size)
+ {
+ PackageVersion result = { 0, 0, 0 };
+ int *versionPtr = &result.major;
+ int digitStart = 0;
+ int i = 0;
+
+ for(; i < (int)versionStr.size; ++i)
+ {
+ char c = versionStr[i];
+ if(isNum(c))
+ {
+ if(digitStart == -1)
+ digitStart = i;
+ }
+ else
+ {
+ int length = i - digitStart;
+ if(digitStart == -1 || length == 0)
+ return Result<PackageVersion>::Err("Package version string is in invalid format. Expected to be in format xxx.xxx.xxx where x is a number");
+
+ *versionPtr = stringToIntNoVerify({ versionStr.data + digitStart, (usize)(i - digitStart) });
+ bool endOfVersions = versionPtr == &result.patch;
+ ++versionPtr;
+ digitStart = -1;
+
+ if(c != '.' || endOfVersions)
+ break;
+ }
+ }
+
+ if(i == 0)
+ return Result<PackageVersion>::Err("version can't be empty");
+
+ if(digitStart != -1)
+ {
+ *versionPtr = stringToIntNoVerify({ versionStr.data + digitStart, (usize)(i - digitStart) });
+ ++versionPtr;
+ }
+
+ if(size)
+ *size = i;
+
+ if(versionPtr == &result.major)
+ return Result<PackageVersion>::Err("version can't be empty");
+
+ return Result<PackageVersion>::Ok(result);
+ }
+
+ VersionTokenizer::VersionTokenizer() :
+ start(nullptr),
+ code(nullptr),
+ size(0),
+ index(0)
+ {
+
+ }
+
+ VersionTokenizer::VersionTokenizer(const char *_start, const usize _size) :
+ start(_start),
+ code(_start),
+ size(_size),
+ index(0)
+ {
+
+ }
+
+ VersionTokenizer::VersionTokenizer(const VersionTokenizer &other)
+ {
+ start = other.start;
+ code = other.code;
+ size = other.size;
+ index = other.index;
+ }
+
+ VersionToken VersionTokenizer::next()
+ {
+ while(index < size)
+ {
+ char c = code[index];
+ if(c == ' ' || c == '\t' || c == '\n' || c == '\r')
+ ++index;
+ else
+ break;
+ }
+
+ if(index >= size)
+ return VersionToken::END_OF_FILE;
+
+ char c = code[index];
+ if(isNum(c))
+ {
+ int versionStrSize = 0;
+ identifier.data = code + index;
+ Result<PackageVersion> packageVersion = parsePackageVersion({ code + index, (usize)(size - index) }, &versionStrSize);
+ identifier.size = versionStrSize;
+ index += versionStrSize;
+ if(!packageVersion)
+ {
+ errMsg = packageVersion.getErrMsg();
+ return VersionToken::INVALID;
+ }
+ version = packageVersion.unwrap();
+ return VersionToken::VERSION_NUMBER;
+ }
+ else if(isAlpha(c))
+ {
+ usize identifierStart = index;
+ ++index;
+ while(index < size && isAlpha(code[index]))
+ {
+ ++index;
+ }
+ usize identifierEnd = index;
+ usize identifierLength = identifierEnd - identifierStart;
+
+ if(identifierLength == 3 && strncmp(code + identifierStart, "and", 3) == 0)
+ {
+ return VersionToken::AND;
+ }
+ else
+ {
+ errMsg = "Invalid identifier ";
+ errMsg += std::string(code + identifierStart, identifierLength);
+ return VersionToken::INVALID;
+ }
+ }
+ else if(c == '<')
+ {
+ ++index;
+ if(index < size && code[index] == '=')
+ {
+ ++index;
+ operation = VersionOperation::LESS_EQUAL;
+ return VersionToken::OPERATION;
+ }
+ operation = VersionOperation::LESS;
+ return VersionToken::OPERATION;
+ }
+ else if(c == '=')
+ {
+ ++index;
+ operation = VersionOperation::EQUAL;
+ return VersionToken::OPERATION;
+ }
+ else if(c == '>')
+ {
+ ++index;
+ if(index < size && code[index] == '=')
+ {
+ ++index;
+ operation = VersionOperation::GREATER_EQUAL;
+ return VersionToken::OPERATION;
+ }
+ operation = VersionOperation::GREATER;
+ return VersionToken::OPERATION;
+ }
+ else
+ {
+ errMsg = "Unexpected character ";
+ errMsg += c;
+ return VersionToken::INVALID;
+ }
+ }
+
+ Result<PackageVersionRange> VersionParser::parse(const char *code, const usize size)
+ {
+ versionRange = PackageVersionRange();
+ tokenizer = VersionTokenizer(code, size);
+ VersionToken token = parseStart();
+
+ if(token == VersionToken::END_OF_FILE)
+ {
+ if(!versionRange.startDefined)
+ return Result<PackageVersionRange>::Err("version can't be empty");
+ return Result<PackageVersionRange>::Ok(versionRange);
+ }
+ else if(token == VersionToken::INVALID)
+ return Result<PackageVersionRange>::Err(tokenizer.errMsg);
+ else
+ {
+ std::string errMsg = "Unexpected token '";
+ switch(token)
+ {
+ case VersionToken::NONE:
+ {
+ errMsg += "<none>";
+ break;
+ }
+ case VersionToken::OPERATION:
+ {
+ errMsg += "operation ";
+ errMsg += asString(tokenizer.operation);
+ break;
+ }
+ case VersionToken::AND:
+ {
+ errMsg += "and";
+ break;
+ }
+ case VersionToken::VERSION_NUMBER:
+ {
+ errMsg += "version ";
+ errMsg += std::string(tokenizer.identifier.data, tokenizer.identifier.size);
+ break;
+ }
+ default:
+ break;
+ }
+ errMsg += "'";
+ return Result<PackageVersionRange>::Err(errMsg);
+ }
+ }
+
+ VersionToken VersionParser::parseStart()
+ {
+ VersionToken token = tokenizer.next();
+ if(token == VersionToken::VERSION_NUMBER)
+ {
+ versionRange.startOperation = VersionOperation::GREATER_EQUAL;
+ versionRange.start = tokenizer.version;
+ versionRange.startDefined = true;
+ token = tokenizer.next();
+ if(token == VersionToken::AND)
+ {
+ token = VersionToken::INVALID;
+ tokenizer.errMsg = "Unexpected end version when start version does not have operation defined";
+ }
+ }
+ else if(token == VersionToken::OPERATION)
+ {
+ versionRange.startOperation = tokenizer.operation;
+ token = tokenizer.next();
+ if(token == VersionToken::VERSION_NUMBER)
+ {
+ versionRange.start = tokenizer.version;
+ versionRange.startDefined = true;
+ switch(versionRange.startOperation)
+ {
+ case VersionOperation::LESS:
+ {
+ token = VersionToken::INVALID;
+ tokenizer.errMsg = "Unexpected version end when start version is expected to less than ";
+ tokenizer.errMsg += versionRange.start.toString();
+ return token;
+ }
+ case VersionOperation::LESS_EQUAL:
+ {
+ token = VersionToken::INVALID;
+ tokenizer.errMsg = "Unexpected version end when start version is expected to be less or equal to ";
+ tokenizer.errMsg += versionRange.start.toString();
+ return token;
+ }
+ case VersionOperation::EQUAL:
+ {
+ token = VersionToken::INVALID;
+ tokenizer.errMsg = "Unexpected version end when start version is expected to be exactly ";
+ tokenizer.errMsg += versionRange.start.toString();
+ return token;
+ }
+ default:
+ break;
+ }
+
+ token = tokenizer.next();
+ if(token == VersionToken::AND)
+ {
+ return parseEnd();
+ }
+ }
+ else if(token == VersionToken::INVALID)
+ return token;
+ else
+ {
+ token = VersionToken::INVALID;
+ tokenizer.errMsg = "Expected version after operation";
+ }
+ }
+ return token;
+ }
+
+ VersionToken VersionParser::parseEnd()
+ {
+ VersionToken token = tokenizer.next();
+ if(token == VersionToken::OPERATION)
+ {
+ versionRange.endOperation = tokenizer.operation;
+ switch(versionRange.endOperation)
+ {
+ case VersionOperation::EQUAL:
+ case VersionOperation::GREATER:
+ case VersionOperation::GREATER_EQUAL:
+ {
+ token = VersionToken::INVALID;
+ tokenizer.errMsg = "End version can only have operations < and <=";
+ return token;
+ }
+ default:
+ break;
+ }
+
+ token = tokenizer.next();
+ if(token == VersionToken::VERSION_NUMBER)
+ {
+ versionRange.end = tokenizer.version;
+ versionRange.endDefined = true;
+ if(versionRange.end <= versionRange.start)
+ {
+ token = VersionToken::INVALID;
+ tokenizer.errMsg = "Expected version end to be greater than ";
+ tokenizer.errMsg += versionRange.start.toString();
+ tokenizer.errMsg += ", was ";
+ tokenizer.errMsg += versionRange.end.toString();
+ return token;
+ }
+ token = tokenizer.next();
+ }
+ else if(token == VersionToken::INVALID)
+ return token;
+ else
+ {
+ token = VersionToken::INVALID;
+ tokenizer.errMsg = "Expected version after operation";
+ }
+ }
+ else
+ {
+ token = VersionToken::INVALID;
+ tokenizer.errMsg = "Expected end version to have operation defined";
+ }
+ return token;
+ }
+}
diff --git a/src/main.cpp b/src/main.cpp
index 4bf840a..5d43679 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1173,7 +1173,7 @@ static int packageProject(int argc, const _tinydir_char_t **argv)
TINYDIR_STRING("\" \"") +
executablePath +
TINYDIR_STRING("\" \"") +
- toFileString(sibsConfig.version) +
+ toFileString(sibsConfig.version.toString()) +
TINYDIR_STRING("\" \"") +
packagePath +
TINYDIR_STRING("\" ") +