Created
April 18, 2015 20:43
-
-
Save Rapptz/500771f134756cfc558a to your computer and use it in GitHub Desktop.
Semantic Versioning Parsing -- http://semver.org/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
0 out of 10 failed parsing | |
0 out of 31 tests incorrectly passed parsing |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <string> | |
#include <cctype> | |
#include <exception> | |
struct invalid_semver : public std::exception { | |
const char* what() const noexcept { | |
return "invalid semantic version provided"; | |
} | |
}; | |
struct version { | |
public: | |
std::string tag; | |
std::string metadata; | |
unsigned major = 0; | |
unsigned minor = 0; | |
unsigned patch = 0; | |
version() = default; | |
version(unsigned major, unsigned minor, unsigned patch = 0): major(major), minor(minor), patch(patch) {} | |
version(const std::string& str) { | |
parse(str); | |
} | |
version(const char* str) { | |
parse(str); | |
} | |
void parse(const std::string& str) { | |
parse_str(str.c_str(), str.size()); | |
} | |
void parse(const char* str) { | |
parse_str(str, std::char_traits<char>::length(str)); | |
} | |
private: | |
bool is_valid_identifier(char c) const noexcept { | |
return std::isalnum(c) || c == '-'; | |
} | |
void parse_str(const char* str, size_t length) { | |
// make sure it doesn't end in an invalid character | |
if(not is_valid_identifier(str[length - 1])) { | |
throw invalid_semver(); | |
} | |
// <major>.<minor>.<patch>[-<dot separated>][+<dot separated>] | |
// <dot separated> = [a-zA-Z0-9-] . [a-zA-Z0-9-] ... | |
size_t index = 0; | |
// parse major.minor.patch | |
parse_number(str, length, index, major); | |
parse_number(str, length, index, minor); | |
parse_number(str, length, index, patch); | |
if(index >= length) { | |
return; | |
} | |
else if(str[index] == '-') { | |
++index; | |
parse_tag_and_meta(str, length, index); | |
} | |
else if(str[index] == '+') { | |
++index; | |
parse_meta(str, length, index); | |
} | |
} | |
void parse_number(const char* str, size_t length, size_t& index, unsigned& number) const { | |
bool first = true; | |
while(index < length) { | |
bool valid_digit = std::isdigit(str[index]); | |
// check for leading zero | |
if(first && str[index] == '0' && (index + 1) < length && std::isdigit(str[index + 1])) { | |
throw invalid_semver(); | |
} | |
// empty number, e.g. negatives, periods, plus signs, etc. | |
if(first && not valid_digit) { | |
throw invalid_semver(); | |
} | |
if(str[index] == '.') { | |
++index; | |
break; | |
} | |
if(str[index] == '-' || str[index] == '+') { | |
break; | |
} | |
if(not valid_digit) { | |
throw invalid_semver(); | |
} | |
number = (number * 10) + (str[index++] - '0'); | |
first = false; | |
} | |
} | |
void parse_tag_and_meta(const char* str, size_t length, size_t& index) { | |
size_t start_pos = index; | |
bool first = true; | |
while(index < length) { | |
if(str[index] == '+') { | |
// this formally ends the tag so process it | |
// make sure it doesn't end in a period | |
if(str[index - 1] == '.') { | |
throw invalid_semver(); | |
} | |
tag.assign(str + start_pos, str + index); | |
++index; | |
return parse_meta(str, length, index); | |
} | |
if(str[index] == '.') { | |
++index; | |
first = true; | |
continue; | |
} | |
// leading zero | |
if(first && str[index] == '0' && (index + 1) < length && std::isdigit(str[index + 1])) { | |
throw invalid_semver(); | |
} | |
if(not is_valid_identifier(str[index])) { | |
throw invalid_semver(); | |
} | |
++index; | |
first = false; | |
} | |
// if we've reached the end of the string then process the tag | |
tag.assign(str + start_pos, str + length); | |
} | |
void parse_meta(const char* str, size_t length, size_t& index) { | |
size_t start_pos = index; | |
while(index < length) { | |
// just verify it's valid | |
if(!(is_valid_identifier(str[index]) || str[index] == '.')) { | |
throw invalid_semver(); | |
} | |
++index; | |
} | |
// since we got this far just assign | |
metadata.assign(str + start_pos, str + length); | |
} | |
}; | |
// returns < 0 if lhs < rhs | |
// returns > 0 if lhs > rhs | |
// returns 0 if lhs == rhs | |
// int compare(const version& lhs, const version& rhs) { | |
// } | |
#include <iostream> | |
void test_success() { | |
auto tests = { "1.0.0-alpha", "1.0.0-alpha.1", "1.0.0-0.3.7", "1.0.0-x.7.z.92", | |
"1.0.0-alpha+001", "1.0.0+20130313144700", "1.0.0-beta+exp.sha.5114f85", | |
"1.0.0", "12312.31232.1231", "2.0.0"}; | |
size_t failed = 0; | |
for(auto&& test : tests) { | |
try { | |
version x(test); | |
} | |
catch(const std::exception&) { | |
// std::cerr << e.what() << '\n'; | |
std::cout << test << " failed parsing\n"; | |
++failed; | |
} | |
} | |
std::cout << failed << " out of " << tests.size() << " failed parsing\n"; | |
} | |
void test_failure() { | |
auto tests = { | |
"", | |
".", | |
"1.", | |
".1", | |
"a.b.c", | |
"1.a.b", | |
"1.1.a", | |
"1.a.1", | |
"a.1.1", | |
"..", | |
"1..", | |
"1.1.", | |
"1..1", | |
"1.1.+123", | |
"1.1.-beta", | |
"-1.1.1", | |
"1.-1.1", | |
"1.1.-1", | |
// Leading zeroes | |
"01.1.1", | |
"001.1.1", | |
"1.01.1", | |
"1.001.1", | |
"1.1.01", | |
"1.1.001", | |
"1.1.1-01", | |
"1.1.1-001", | |
"1.1.1-beta.01", | |
"1.1.1-beta.001", | |
"0.1-2.3.4", | |
"-12312.122-12312", | |
"-1.2.3" | |
}; | |
size_t success = 0; | |
for(auto&& test : tests) { | |
try { | |
version x(test); | |
++success; | |
std::cout << test << " incorrectly passed parsing\n"; | |
} | |
catch(const std::exception&) { | |
// correctly failed | |
} | |
} | |
std::cout << success << " out of " << tests.size() << " tests incorrectly passed parsing\n"; | |
} | |
int main() { | |
test_success(); | |
test_failure(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment