From f77a4ef2c67c20a1abddfdd595d6db30149487b3 Mon Sep 17 00:00:00 2001 From: Moustafa El-Sawy Date: Wed, 12 Jun 2024 18:56:33 -0400 Subject: [PATCH 1/2] Improved AppCastMaker and AppCastMakerTests --- .../AppCastMakerTests.cs | 73 ++++- .../AppCastMaker.cs | 264 ++++++++++++++---- 2 files changed, 277 insertions(+), 60 deletions(-) diff --git a/src/NetSparkle.Tests.AppCastGenerator/AppCastMakerTests.cs b/src/NetSparkle.Tests.AppCastGenerator/AppCastMakerTests.cs index 4412cfe2..c0fd9d73 100644 --- a/src/NetSparkle.Tests.AppCastGenerator/AppCastMakerTests.cs +++ b/src/NetSparkle.Tests.AppCastGenerator/AppCastMakerTests.cs @@ -72,7 +72,7 @@ public void CanGetVersionFromName() Assert.Null(AppCastMaker.GetVersionFromName("foo1.")); Assert.Equal("1.0", AppCastMaker.GetVersionFromName("hello 1.0.txt")); Assert.Equal("1.0", AppCastMaker.GetVersionFromName("hello 1.0 .txt")); // whitespace shouldn't matter - Assert.Equal("0", AppCastMaker.GetVersionFromName("hello 1 .0.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("hello 1 .0.txt")); // I changed this to null as I think its a more suitable output versus a version of 0 Assert.Equal("2.3", AppCastMaker.GetVersionFromName("hello a2.3.txt")); Assert.Equal("4.3.2", AppCastMaker.GetVersionFromName("My Favorite App 4.3.2.zip")); Assert.Equal("1.0", AppCastMaker.GetVersionFromName("foo1.0")); @@ -92,8 +92,75 @@ public void CanGetVersionFromName() Assert.Equal("1.0", AppCastMaker.GetVersionFromName("hello 1.0.tar.gz")); Assert.Equal("4.3.2", AppCastMaker.GetVersionFromName("My Favorite App 4.3.2.tar.gz")); Assert.Equal("0.0.0", AppCastMaker.GetVersionFromName("My Favorite Tools (Linux-x64) 0.0.0.tar.gz")); - // semantic - //Assert.Equal("1.1.0", AppCastMaker.GetVersionFromName("1.1.0interruption.1.exe")); + + // Semantic version tests + // Test cases are from https://github.com/semver/semver/issues/232 + // Valid semantic version tests + Assert.Equal("0.0.4", AppCastMaker.GetVersionFromName("app 0.0.4.txt")); + Assert.Equal("10.20.30", AppCastMaker.GetVersionFromName("app 10.20.30.txt")); + Assert.Equal("1.1.2-prerelease+meta", AppCastMaker.GetVersionFromName("app 1.1.2-prerelease+meta.txt")); + Assert.Equal("1.1.2+meta", AppCastMaker.GetVersionFromName("app 1.1.2+meta.txt")); + Assert.Equal("1.1.2+meta-valid", AppCastMaker.GetVersionFromName("app 1.1.2+meta-valid.txt")); + Assert.Equal("1.0.0-alpha", AppCastMaker.GetVersionFromName("app 1.0.0-alpha.txt")); + Assert.Equal("1.0.0-beta", AppCastMaker.GetVersionFromName("app 1.0.0-beta.txt")); + Assert.Equal("1.0.0-alpha.beta", AppCastMaker.GetVersionFromName("app 1.0.0-alpha.beta.txt")); + Assert.Equal("1.0.0-alpha.beta.1", AppCastMaker.GetVersionFromName("app 1.0.0-alpha.beta.1.txt")); + Assert.Equal("1.0.0-alpha.1", AppCastMaker.GetVersionFromName("app 1.0.0-alpha.1.txt")); + Assert.Equal("1.0.0-alpha0.valid", AppCastMaker.GetVersionFromName("app 1.0.0-alpha0.valid.txt")); + Assert.Equal("1.0.0-alpha.0valid", AppCastMaker.GetVersionFromName("app 1.0.0-alpha.0valid.txt")); + Assert.Equal("1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay", AppCastMaker.GetVersionFromName("app 1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay.txt")); + Assert.Equal("1.0.0-rc.1+build.1", AppCastMaker.GetVersionFromName("app 1.0.0-rc.1+build.1.txt")); + Assert.Equal("2.0.0-rc.1+build.123", AppCastMaker.GetVersionFromName("app 2.0.0-rc.1+build.123.txt")); + Assert.Equal("1.2.3-beta", AppCastMaker.GetVersionFromName("app 1.2.3-beta.txt")); + Assert.Equal("10.2.3-DEV-SNAPSHOT", AppCastMaker.GetVersionFromName("app 10.2.3-DEV-SNAPSHOT.txt")); + Assert.Equal("1.2.3-SNAPSHOT-123", AppCastMaker.GetVersionFromName("app 1.2.3-SNAPSHOT-123.txt")); + Assert.Equal("1.0.0", AppCastMaker.GetVersionFromName("app 1.0.0.txt")); + Assert.Equal("2.0.0", AppCastMaker.GetVersionFromName("app 2.0.0.txt")); + Assert.Equal("1.1.7", AppCastMaker.GetVersionFromName("app 1.1.7.txt")); + Assert.Equal("2.0.0+build.1848", AppCastMaker.GetVersionFromName("app 2.0.0+build.1848.txt")); + Assert.Equal("2.0.1-alpha.1227", AppCastMaker.GetVersionFromName("app 2.0.1-alpha.1227.txt")); + Assert.Equal("1.0.0-alpha+beta", AppCastMaker.GetVersionFromName("app 1.0.0-alpha+beta.txt")); + Assert.Equal("1.2.3----RC-SNAPSHOT.12.9.1--.12+788", AppCastMaker.GetVersionFromName("app 1.2.3----RC-SNAPSHOT.12.9.1--.12+788.txt")); + Assert.Equal("1.2.3----R-S.12.9.1--.12+meta", AppCastMaker.GetVersionFromName("app 1.2.3----R-S.12.9.1--.12+meta.txt")); + Assert.Equal("1.2.3----RC-SNAPSHOT.12.9.1--.12", AppCastMaker.GetVersionFromName("app 1.2.3----RC-SNAPSHOT.12.9.1--.12.txt")); + Assert.Equal("1.0.0+0.build.1-rc.10000aaa-kk-0.1", AppCastMaker.GetVersionFromName("app 1.0.0+0.build.1-rc.10000aaa-kk-0.1.txt")); + Assert.Equal("99999999999999999999999.999999999999999999.99999999999999999", AppCastMaker.GetVersionFromName("app 99999999999999999999999.999999999999999999.99999999999999999.txt")); + Assert.Equal("1.0.0-0A.is.legal", AppCastMaker.GetVersionFromName("app 1.0.0-0A.is.legal.txt")); + + // Invalid semantic versions tests + Assert.Null(AppCastMaker.GetVersionFromName("app 1.2.3-0123.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app 1.2.3-0123.0123.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app 1.1.2+.123.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app +invalid.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app -invalid.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app -invalid+invalid.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app -invalid.01.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app alpha.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app alpha.beta.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app alpha.beta.1.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app alpha.1.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app alpha+beta.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app alpha_beta.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app alpha..txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app beta.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app 1.0.0-alpha_beta.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app -alpha.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app 1.0.0-alpha..txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app 1.0.0-alpha..1.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app 1.0.0-alpha...1.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app 1.0.0-alpha....1.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app 1.0.0-alpha.....1.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app 1.0.0-alpha......1.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app 1.0.0-alpha.......1.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app 1.2.3.DEV.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app 1.2-SNAPSHOT.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app 1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app 1.2-RC-SNAPSHOT.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app -1.0.3-gamma+b7718.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app +justmeta.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app 9.8.7+meta+meta.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app 9.8.7-whatever+meta+meta.txt")); + Assert.Null(AppCastMaker.GetVersionFromName("app 99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12.txt")); } [Fact] diff --git a/src/NetSparkle.Tools.AppCastGenerator/AppCastMaker.cs b/src/NetSparkle.Tools.AppCastGenerator/AppCastMaker.cs index a9cb5406..5f9309dd 100644 --- a/src/NetSparkle.Tools.AppCastGenerator/AppCastMaker.cs +++ b/src/NetSparkle.Tools.AppCastGenerator/AppCastMaker.cs @@ -54,95 +54,245 @@ public AppCastMaker(SignatureManager signatureManager, Options options) public static string GetVersionFromName(string fullFileNameWithPath, string binaryDirectory = "") { - // get the numbers at the end of the string in case the app is something like 1.0application1.0.0.dmg - // this solution is a mix of https://stackoverflow.com/a/22704755/3938401 - // and https://stackoverflow.com/a/31926058/3938401 - // basically, we pull out the last numbers out of the name that are separated by '.' + // File name is empty if (string.IsNullOrWhiteSpace(fullFileNameWithPath)) { return null; } + + // Filename has no extension or ends in . if (fullFileNameWithPath.EndsWith(".")) { - // don't allow raw files that end in '.' return null; } - // don't search above initial binary directory + if (!string.IsNullOrWhiteSpace(binaryDirectory)) { - fullFileNameWithPath = fullFileNameWithPath.Replace(binaryDirectory, ""); + fullFileNameWithPath = fullFileNameWithPath.Replace(binaryDirectory, "").Trim(); + } + + // Handle complex extensions and remove them if they exist + string[] extensionPatterns = { @"\.tar\.gz$", @"\.tar$", @"\.gz$", @"\.zip$", @"\.txt$", @"\.exe$", @"\.bin$", @"\.msi$", @"\.excel", @"\.mcdx", @"\.pdf", @"\.dll", @"\.ted" }; + foreach (var pattern in extensionPatterns) + { + if (Regex.IsMatch(fullFileNameWithPath, pattern)) + { + fullFileNameWithPath = Regex.Replace(fullFileNameWithPath, pattern, "").Trim(); + break; + } + } + + // Replace multiple spaces with a single space + fullFileNameWithPath = Regex.Replace(fullFileNameWithPath, @"\s+", " "); + + // Regex for simple version numbers (X.X.X or X.X.X.X) + string simpleVersionPattern = @"^\d+(\.\d+){1,3}$"; + + // Regex for semantic versioning + string semverPattern = @"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)" + + @"(-((0|[1-9]\d*)|\d*[a-zA-Z-][0-9a-zA-Z-]*)" + + @"(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?" + + @"(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$"; + + // Function to check if a segment is a valid version + bool IsValidVersion(string segment) + { + return Regex.IsMatch(segment, simpleVersionPattern) || Regex.IsMatch(segment, semverPattern); } - var folderSplit = fullFileNameWithPath.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); - var numFolderSectionsChecked = 0; - var nums = new List(); - for (int j = folderSplit.Length - 1; j >= 0; j--) - { - var split = folderSplit[j].Split('.', StringSplitOptions.RemoveEmptyEntries); - // start on last item and go until first item in file name - for (int i = split.Length - 1; i >= 0; i--) + + // This regex finds the first text block that is not preceded by a + or - and is followed by a number (starting from left) + string RemoveTextBlockFromLeft(string input) + { + if (!Regex.IsMatch(input, @"\d")) { - var splitItem = split[i]; - var foundAtEnd = false; - if (int.TryParse(splitItem, out int temp)) + return ""; + } + + if (Regex.IsMatch(input, @"[+-]")) + { + var match = Regex.Match(input, @"(? 1) + { + leftPart = parts[0]; + rightPart = parts[^1]; + } + else + { + leftPart = parts[0]; + rightPart = parts[0]; + } + + // Checking left part with logic from left + string lastValidVersionLeft = null; + if (!string.IsNullOrEmpty(leftPart)) + { + // Remove any text block from left + // For example 0.1foo becomes 0.1, 0.1-foo stays 0.1-foo, 0.1+foo stays 0.1+foo + leftPart = RemoveTextBlockFromLeft(leftPart); + + // Make sure leftpart has a number + if (Regex.IsMatch(leftPart, @"\d")) + { + // Check if its only numeric values and a simple version for quick check + if (Regex.IsMatch(leftPart, @"^[\d.]+$") && IsValidVersion(leftPart)) + { + lastValidVersionLeft = leftPart; } else { - // look at the end of the string by default, then the start of the string - var regexPatternEndOfStr = @"\d+$"; - var regexEndOfStr = new Regex(regexPatternEndOfStr); - var matchEndOfStr = regexEndOfStr.Match(splitItem); - if (matchEndOfStr.Success) + // Its more complex so we check if its semantic version before splitting + if (IsValidVersion(leftPart)) { - var matchNum = int.Parse(matchEndOfStr.Captures[^1].Value); - nums.Add(matchNum); - foundAtEnd = true; + lastValidVersionLeft = leftPart; } else { - // look at start of string instead - // in case we get something like 3 foo bar - var regexPatternStartOfStr = @"(^\d+)"; - var regexStartOfStr = new Regex(regexPatternStartOfStr); - var startOfStrMatch = regexStartOfStr.Match(splitItem); - if (startOfStrMatch.Success) + // Start splitting and going from left to right + // Keep record of last applicable version and check one more segment after it, if it fails then the last one we found is what we need + var segments = leftPart.Split('.'); + string tempSegment = ""; + bool lastVersionToCheck = false; + for (int i = segments.Length - 1; i >= 0; i--) { - var matchNum = int.Parse(startOfStrMatch.Captures[^1].Value); - nums.Add(matchNum); + var segment = segments[i]; + if (Regex.IsMatch(segment, @"[a-zA-Z]") && Regex.IsMatch(segment, @"\d")) + { + var match = Regex.Match(segment, @"[^+-]*[a-zA-Z]"); + if (match.Success) + { + segment = segment.Substring(match.Index + match.Length); + lastVersionToCheck = true; + } + } + + tempSegment = string.IsNullOrEmpty(tempSegment) ? segment : segment + "." + tempSegment; + tempSegment = tempSegment.Trim('.'); + + if (IsValidVersion(tempSegment)) + { + lastValidVersionLeft = tempSegment; + } + + if (lastVersionToCheck) + { + break; + } } } } - if (splitItem.Contains(" ") && foundAtEnd) + } + } + + // Checking right part with logic from right + string lastValidVersionRight = null; + if (!string.IsNullOrEmpty(rightPart)) + { + // Remove any text block from right + // For example foo0.1 becomes 0.1 + rightPart = RemoveTextBlockFromRight(rightPart); + + // Make sure rightpart has a number + if (Regex.IsMatch(rightPart, @"\d")) + { + // Check if its only numeric values and a simple version for quick check + if (Regex.IsMatch(rightPart, @"^[\d.]+$") && IsValidVersion(rightPart)) { - // item had a space, so we're going to assume that the version was at the end - // of the string - break; + lastValidVersionRight = rightPart; } - if (nums.Count >= 4) + else { - break; // Major.Minor.Revision.Patch is all we allow + // Its more complex so we check if its semantic version before splitting + if (IsValidVersion(rightPart)) + { + lastValidVersionRight = rightPart; + } + else + { + // Start splitting and going from left to right + // Keep record of last applicable version and check one more segment after it, if it fails then the last one we found is what we need + var segments = rightPart.Split('.'); + string tempSegment = ""; + bool lastVersionToCheck = false; + for (int i = segments.Length - 1; i >= 0; i--) + { + var segment = segments[i]; + if (Regex.IsMatch(segment, @"[a-zA-Z]") && Regex.IsMatch(segment, @"\d")) + { + var match = Regex.Match(segment, @"[^+-]*[a-zA-Z]"); + if (match.Success) + { + segment = segment.Substring(match.Index + match.Length); + lastVersionToCheck = true; + } + } + + tempSegment = string.IsNullOrEmpty(tempSegment) ? segment : segment + "." + tempSegment; + tempSegment = tempSegment.Trim('.'); + + if (IsValidVersion(tempSegment)) + { + lastValidVersionRight = tempSegment; + } + + if (lastVersionToCheck) + { + break; + } + } + } } } - if (nums.Count > 0) - { - // we found some part of a version number we can use, bail out! - break; - } - numFolderSectionsChecked++; - // check up to 4 folders -- 4 is arbitrary but we don't want to - // crawl up the entire folder directory tree/path - if (numFolderSectionsChecked >= 4) - { - break; - } } - if (nums.Count > 0) + + // Right part is preferred over left part + if (lastValidVersionRight != null) + { + return lastValidVersionRight; + } + else if (lastValidVersionLeft != null) { - nums.Reverse(); - return string.Join('.', nums); + return lastValidVersionLeft; + } + else + { + return null; } - return null; } public static string GetVersionFromAssembly(string fullFileNameWithPath) From 2a18bfc9d8372b3918e00ee6368c58220420df5a Mon Sep 17 00:00:00 2001 From: Moustafa El-Sawy Date: Thu, 13 Jun 2024 08:44:49 -0400 Subject: [PATCH 2/2] Included folder path in GetNameFromVersion --- .../AppCastMakerTests.cs | 9 +- .../AppCastMaker.cs | 226 +++++++++--------- 2 files changed, 123 insertions(+), 112 deletions(-) diff --git a/src/NetSparkle.Tests.AppCastGenerator/AppCastMakerTests.cs b/src/NetSparkle.Tests.AppCastGenerator/AppCastMakerTests.cs index c0fd9d73..25a38f09 100644 --- a/src/NetSparkle.Tests.AppCastGenerator/AppCastMakerTests.cs +++ b/src/NetSparkle.Tests.AppCastGenerator/AppCastMakerTests.cs @@ -70,6 +70,7 @@ public void CanGetVersionFromName() Assert.Null(AppCastMaker.GetVersionFromName(null)); Assert.Null(AppCastMaker.GetVersionFromName("foo")); Assert.Null(AppCastMaker.GetVersionFromName("foo1.")); + Assert.Null(AppCastMaker.GetVersionFromName("hello 1.txt")); // New test, 1 is not a valid version, should be atleast Major.Minor Assert.Equal("1.0", AppCastMaker.GetVersionFromName("hello 1.0.txt")); Assert.Equal("1.0", AppCastMaker.GetVersionFromName("hello 1.0 .txt")); // whitespace shouldn't matter Assert.Null(AppCastMaker.GetVersionFromName("hello 1 .0.txt")); // I changed this to null as I think its a more suitable output versus a version of 0 @@ -77,6 +78,8 @@ public void CanGetVersionFromName() Assert.Equal("4.3.2", AppCastMaker.GetVersionFromName("My Favorite App 4.3.2.zip")); Assert.Equal("1.0", AppCastMaker.GetVersionFromName("foo1.0")); Assert.Equal("0.1", AppCastMaker.GetVersionFromName("foo0.1")); + Assert.Equal("0.1", AppCastMaker.GetVersionFromName("foo 0.1")); + Assert.Equal("0.1", AppCastMaker.GetVersionFromName("foo_0.1")); Assert.Equal("0.1", AppCastMaker.GetVersionFromName("0.1foo")); Assert.Equal("0.1", AppCastMaker.GetVersionFromName("0.1 My App")); Assert.Equal("0.0.3.1", AppCastMaker.GetVersionFromName("foo0.0.3.1")); @@ -550,7 +553,7 @@ public void SingleDigitVersionDoesNotFail() // setup test dir var tempDir = GetCleanTempDir(); // create dummy files - var dummyFilePath = Path.Combine(tempDir, "hello 1.txt"); + var dummyFilePath = Path.Combine(tempDir, "hello 1.0.txt"); const int fileSizeBytes = 57; var tempData = RandomString(fileSizeBytes); File.WriteAllText(dummyFilePath, tempData); @@ -582,8 +585,8 @@ public void SingleDigitVersionDoesNotFail() } Assert.Single(items); - Assert.Equal("1", items[0].Version); - Assert.Equal("https://example.com/downloads/hello%201.txt", items[0].DownloadLink); + Assert.Equal("1.0", items[0].Version); + Assert.Equal("https://example.com/downloads/hello%201.0.txt", items[0].DownloadLink); Assert.True(items[0].DownloadSignature.Length > 0); Assert.True(items[0].IsWindowsUpdate); Assert.Equal(fileSizeBytes, items[0].UpdateSize); diff --git a/src/NetSparkle.Tools.AppCastGenerator/AppCastMaker.cs b/src/NetSparkle.Tools.AppCastGenerator/AppCastMaker.cs index 5f9309dd..61522b0d 100644 --- a/src/NetSparkle.Tools.AppCastGenerator/AppCastMaker.cs +++ b/src/NetSparkle.Tools.AppCastGenerator/AppCastMaker.cs @@ -85,6 +85,9 @@ public static string GetVersionFromName(string fullFileNameWithPath, string bina // Replace multiple spaces with a single space fullFileNameWithPath = Regex.Replace(fullFileNameWithPath, @"\s+", " "); + // Replace _ with space + fullFileNameWithPath = fullFileNameWithPath.Replace("_", " "); + // Regex for simple version numbers (X.X.X or X.X.X.X) string simpleVersionPattern = @"^\d+(\.\d+){1,3}$"; @@ -139,160 +142,165 @@ string RemoveTextBlockFromRight(string input) return input; } - // Split the filename by space to find the version segment - var parts = fullFileNameWithPath.Split(' '); + var folderSplit = fullFileNameWithPath.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); - // If there are multiple parts, we check the first and last parts only assuming version is in either - // If the strings in the start and end both produce valid versions, we return the version from the end - // If single string no spaces, we take the entire string and check it from left and right - string leftPart = null; - string rightPart = null; - if (parts.Length > 1) - { - leftPart = parts[0]; - rightPart = parts[^1]; - } - else + // Loop through the last 4 folder names to find the version + for (int j = folderSplit.Length - 1; j >= Math.Max(0, folderSplit.Length - 4); j--) { - leftPart = parts[0]; - rightPart = parts[0]; - } + var fileName = folderSplit[j]; - // Checking left part with logic from left - string lastValidVersionLeft = null; - if (!string.IsNullOrEmpty(leftPart)) - { - // Remove any text block from left - // For example 0.1foo becomes 0.1, 0.1-foo stays 0.1-foo, 0.1+foo stays 0.1+foo - leftPart = RemoveTextBlockFromLeft(leftPart); + // Split the filename by space to find the version segment + var parts = fileName.Split(' '); - // Make sure leftpart has a number - if (Regex.IsMatch(leftPart, @"\d")) + // If there are multiple parts, we check the first and last parts only assuming version is in either + // If the strings in the start and end both produce valid versions, we return the version from the end + // If single string no spaces, we take the entire string and check it from left and right + string leftPart = null; + string rightPart = null; + if (parts.Length > 1) { - // Check if its only numeric values and a simple version for quick check - if (Regex.IsMatch(leftPart, @"^[\d.]+$") && IsValidVersion(leftPart)) - { - lastValidVersionLeft = leftPart; - } - else + leftPart = parts[0]; + rightPart = parts[^1]; + } + else + { + leftPart = parts[0]; + rightPart = parts[0]; + } + + // Checking left part with logic from left + string lastValidVersionLeft = null; + if (!string.IsNullOrEmpty(leftPart)) + { + // Remove any text block from left + // For example 0.1foo becomes 0.1, 0.1-foo stays 0.1-foo, 0.1+foo stays 0.1+foo + leftPart = RemoveTextBlockFromLeft(leftPart); + + // Make sure leftpart has a number + if (Regex.IsMatch(leftPart, @"\d")) { - // Its more complex so we check if its semantic version before splitting - if (IsValidVersion(leftPart)) + // Check if its only numeric values and a simple version for quick check + if (Regex.IsMatch(leftPart, @"^[\d.]+$") && IsValidVersion(leftPart)) { lastValidVersionLeft = leftPart; } else { - // Start splitting and going from left to right - // Keep record of last applicable version and check one more segment after it, if it fails then the last one we found is what we need - var segments = leftPart.Split('.'); - string tempSegment = ""; - bool lastVersionToCheck = false; - for (int i = segments.Length - 1; i >= 0; i--) + // Its more complex so we check if its semantic version before splitting + if (IsValidVersion(leftPart)) + { + lastValidVersionLeft = leftPart; + } + else { - var segment = segments[i]; - if (Regex.IsMatch(segment, @"[a-zA-Z]") && Regex.IsMatch(segment, @"\d")) + // Start splitting and going from left to right + // Keep record of last applicable version and check one more segment after it, if it fails then the last one we found is what we need + var segments = leftPart.Split('.'); + string tempSegment = ""; + bool lastVersionToCheck = false; + for (int i = segments.Length - 1; i >= 0; i--) { - var match = Regex.Match(segment, @"[^+-]*[a-zA-Z]"); - if (match.Success) + var segment = segments[i]; + if (Regex.IsMatch(segment, @"[a-zA-Z]") && Regex.IsMatch(segment, @"\d")) { - segment = segment.Substring(match.Index + match.Length); - lastVersionToCheck = true; + var match = Regex.Match(segment, @"[^+-]*[a-zA-Z]"); + if (match.Success) + { + segment = segment.Substring(match.Index + match.Length); + lastVersionToCheck = true; + } } - } - tempSegment = string.IsNullOrEmpty(tempSegment) ? segment : segment + "." + tempSegment; - tempSegment = tempSegment.Trim('.'); + tempSegment = string.IsNullOrEmpty(tempSegment) ? segment : segment + "." + tempSegment; + tempSegment = tempSegment.Trim('.'); - if (IsValidVersion(tempSegment)) - { - lastValidVersionLeft = tempSegment; - } + if (IsValidVersion(tempSegment)) + { + lastValidVersionLeft = tempSegment; + } - if (lastVersionToCheck) - { - break; + if (lastVersionToCheck) + { + break; + } } } } } } - } - // Checking right part with logic from right - string lastValidVersionRight = null; - if (!string.IsNullOrEmpty(rightPart)) - { - // Remove any text block from right - // For example foo0.1 becomes 0.1 - rightPart = RemoveTextBlockFromRight(rightPart); - - // Make sure rightpart has a number - if (Regex.IsMatch(rightPart, @"\d")) + // Checking right part with logic from right + string lastValidVersionRight = null; + if (!string.IsNullOrEmpty(rightPart)) { - // Check if its only numeric values and a simple version for quick check - if (Regex.IsMatch(rightPart, @"^[\d.]+$") && IsValidVersion(rightPart)) - { - lastValidVersionRight = rightPart; - } - else + // Remove any text block from right + // For example foo0.1 becomes 0.1 + rightPart = RemoveTextBlockFromRight(rightPart); + + // Make sure rightpart has a number + if (Regex.IsMatch(rightPart, @"\d")) { - // Its more complex so we check if its semantic version before splitting - if (IsValidVersion(rightPart)) + // Check if its only numeric values and a simple version for quick check + if (Regex.IsMatch(rightPart, @"^[\d.]+$") && IsValidVersion(rightPart)) { lastValidVersionRight = rightPart; } else { - // Start splitting and going from left to right - // Keep record of last applicable version and check one more segment after it, if it fails then the last one we found is what we need - var segments = rightPart.Split('.'); - string tempSegment = ""; - bool lastVersionToCheck = false; - for (int i = segments.Length - 1; i >= 0; i--) + // Its more complex so we check if its semantic version before splitting + if (IsValidVersion(rightPart)) + { + lastValidVersionRight = rightPart; + } + else { - var segment = segments[i]; - if (Regex.IsMatch(segment, @"[a-zA-Z]") && Regex.IsMatch(segment, @"\d")) + // Start splitting and going from left to right + // Keep record of last applicable version and check one more segment after it, if it fails then the last one we found is what we need + var segments = rightPart.Split('.'); + string tempSegment = ""; + bool lastVersionToCheck = false; + for (int i = segments.Length - 1; i >= 0; i--) { - var match = Regex.Match(segment, @"[^+-]*[a-zA-Z]"); - if (match.Success) + var segment = segments[i]; + if (Regex.IsMatch(segment, @"[a-zA-Z]") && Regex.IsMatch(segment, @"\d")) { - segment = segment.Substring(match.Index + match.Length); - lastVersionToCheck = true; + var match = Regex.Match(segment, @"[^+-]*[a-zA-Z]"); + if (match.Success) + { + segment = segment.Substring(match.Index + match.Length); + lastVersionToCheck = true; + } } - } - tempSegment = string.IsNullOrEmpty(tempSegment) ? segment : segment + "." + tempSegment; - tempSegment = tempSegment.Trim('.'); + tempSegment = string.IsNullOrEmpty(tempSegment) ? segment : segment + "." + tempSegment; + tempSegment = tempSegment.Trim('.'); - if (IsValidVersion(tempSegment)) - { - lastValidVersionRight = tempSegment; - } + if (IsValidVersion(tempSegment)) + { + lastValidVersionRight = tempSegment; + } - if (lastVersionToCheck) - { - break; + if (lastVersionToCheck) + { + break; + } } } } } } - } - // Right part is preferred over left part - if (lastValidVersionRight != null) - { - return lastValidVersionRight; - } - else if (lastValidVersionLeft != null) - { - return lastValidVersionLeft; - } - else - { - return null; + // Right part is preferred over left part + if (lastValidVersionRight != null) + { + return lastValidVersionRight; + } + else if (lastValidVersionLeft != null) + { + return lastValidVersionLeft; + } } + return null; } public static string GetVersionFromAssembly(string fullFileNameWithPath)