diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/CmakeModule.cpp | 1 | ||||
-rw-r--r-- | src/Conf.cpp | 23 | ||||
-rw-r--r-- | src/FileUtil.cpp | 27 | ||||
-rw-r--r-- | src/GlobalLib.cpp | 40 | ||||
-rw-r--r-- | src/Package.cpp | 65 | ||||
-rw-r--r-- | src/PkgConfig.cpp | 48 | ||||
-rw-r--r-- | src/Version.cpp | 86 | ||||
-rw-r--r-- | src/VersionParser.cpp | 362 | ||||
-rw-r--r-- | src/main.cpp | 2 |
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("\" ") + |