diff --git a/src/SlnParser.Tests/IntegrationTests.cs b/src/SlnParser.Tests/IntegrationTests.cs index b391dc7..08e761f 100644 --- a/src/SlnParser.Tests/IntegrationTests.cs +++ b/src/SlnParser.Tests/IntegrationTests.cs @@ -9,6 +9,53 @@ namespace SlnParser.Tests { public class IntegrationTests { + [Fact] + public void Parse_WithEmptySolutionFile_IsParsedCorrectly() + { + var solutionFile = LoadSolution("Empty"); + + var sut = new SolutionParser(); + + var solution = sut.Parse(solutionFile); + + solution + .FileFormatVersion + .Should() + .Be(string.Empty); + + var visualStudioVersion = solution.VisualStudioVersion; + + visualStudioVersion + .MinimumVersion + .Should() + .Be(string.Empty); + + visualStudioVersion + .Version + .Should() + .Be(string.Empty); + + solution + .Guid + .Should() + .Be(null); + + solution + .ConfigurationPlatforms + .Should() + .HaveCount(0); + + solution + .AllProjects + .Should() + .HaveCount(0); + + solution + .Projects + .Should() + .HaveCount(0); + } + [Fact] [Category("ParseSolution:SlnParser")] public void Should_Be_Able_To_Parse_SlnParser_Solution_Correctly() @@ -371,7 +418,22 @@ public void Parse_WithProjectWithoutPlatform_IsParsedCorrectly() project.TypeGuid.Should().Be("D183A3D8-5FD8-494B-B014-37F57B35E655"); project.Type.Should().Be(ProjectType.Unknown); } + + [Fact] + public void Parse_WithSolutionGuid_IsParsedCorrectly() + { + var solutionFile = LoadSolution("SolutionGuid"); + + var sut = new SolutionParser(); + var solution = sut.Parse(solutionFile); + + solution + .Guid + .Should() + .Be("7F92F20E-4C3D-4316-BF60-105559EFEAFF"); + } + private static FileInfo LoadSolution(string solutionName) { var solutionFileName = $"./Solutions/{solutionName}.sln"; diff --git a/src/SlnParser.Tests/Solutions/Empty.sln b/src/SlnParser.Tests/Solutions/Empty.sln new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/src/SlnParser.Tests/Solutions/Empty.sln @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/SlnParser.Tests/Solutions/SolutionGuid.sln b/src/SlnParser.Tests/Solutions/SolutionGuid.sln new file mode 100644 index 0000000..352dc7d --- /dev/null +++ b/src/SlnParser.Tests/Solutions/SolutionGuid.sln @@ -0,0 +1,5 @@ +Global + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7F92F20E-4C3D-4316-BF60-105559EFEAFF} + EndGlobalSection +EndGlobal \ No newline at end of file diff --git a/src/SlnParser/Contracts/Helper/IParseSolutionConfigurationPlatform.cs b/src/SlnParser/Contracts/Helper/IParseSolutionConfigurationPlatform.cs index 86a6b18..9436b78 100644 --- a/src/SlnParser/Contracts/Helper/IParseSolutionConfigurationPlatform.cs +++ b/src/SlnParser/Contracts/Helper/IParseSolutionConfigurationPlatform.cs @@ -6,6 +6,6 @@ internal interface IParseSolutionConfigurationPlatform { IEnumerable Parse( IEnumerable fileContents, - string startSection); + string sectionName); } } diff --git a/src/SlnParser/Contracts/Helper/ISectionParser.cs b/src/SlnParser/Contracts/Helper/ISectionParser.cs new file mode 100644 index 0000000..e40ce6d --- /dev/null +++ b/src/SlnParser/Contracts/Helper/ISectionParser.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace SlnParser.Contracts.Helper +{ + internal interface ISectionParser + { + IEnumerable GetFileContentsInGlobalSection( + IEnumerable fileContents, + string sectionName); + } +} diff --git a/src/SlnParser/Contracts/ISolution.cs b/src/SlnParser/Contracts/ISolution.cs index 691ea40..0b5c15c 100644 --- a/src/SlnParser/Contracts/ISolution.cs +++ b/src/SlnParser/Contracts/ISolution.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; namespace SlnParser.Contracts @@ -42,5 +43,10 @@ public interface ISolution /// The s configured for this solution /// IReadOnlyCollection ConfigurationPlatforms { get; } + + /// + /// The of the solution. + /// + Guid? Guid { get; } } } diff --git a/src/SlnParser/Contracts/Solution.cs b/src/SlnParser/Contracts/Solution.cs index 6add029..ac5241d 100644 --- a/src/SlnParser/Contracts/Solution.cs +++ b/src/SlnParser/Contracts/Solution.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; namespace SlnParser.Contracts @@ -36,5 +37,8 @@ public Solution() /// public IReadOnlyCollection ConfigurationPlatforms { get; internal set; } + + /// + public Guid? Guid { get; internal set; } } } diff --git a/src/SlnParser/Helper/EnrichSolutionWithProjectConfigurationPlatforms.cs b/src/SlnParser/Helper/EnrichSolutionWithProjectConfigurationPlatforms.cs index 0a7b0e2..cbe089b 100644 --- a/src/SlnParser/Helper/EnrichSolutionWithProjectConfigurationPlatforms.cs +++ b/src/SlnParser/Helper/EnrichSolutionWithProjectConfigurationPlatforms.cs @@ -19,7 +19,7 @@ public void Enrich(Solution solution, IEnumerable fileContents) { var projectConfigurations = _parseSolutionConfigurationPlatform.Parse( fileContents, - "GlobalSection(ProjectConfiguration"); + "ProjectConfiguration"); MapConfigurationPlatformsToProjects(solution, projectConfigurations); } diff --git a/src/SlnParser/Helper/EnrichSolutionWithSolutionConfigurationPlatforms.cs b/src/SlnParser/Helper/EnrichSolutionWithSolutionConfigurationPlatforms.cs index cc44233..cddb7be 100644 --- a/src/SlnParser/Helper/EnrichSolutionWithSolutionConfigurationPlatforms.cs +++ b/src/SlnParser/Helper/EnrichSolutionWithSolutionConfigurationPlatforms.cs @@ -18,7 +18,7 @@ public void Enrich(Solution solution, IEnumerable fileContents) { var projectConfigurations = _parseSolutionConfigurationPlatform.Parse( fileContents, - "GlobalSection(SolutionConfiguration"); + "SolutionConfiguration"); solution.ConfigurationPlatforms = projectConfigurations .Select(projectConfiguration => projectConfiguration.ConfigurationPlatform) .ToList() diff --git a/src/SlnParser/Helper/EnrichSolutionWithSolutionGuid.cs b/src/SlnParser/Helper/EnrichSolutionWithSolutionGuid.cs new file mode 100644 index 0000000..e69ee12 --- /dev/null +++ b/src/SlnParser/Helper/EnrichSolutionWithSolutionGuid.cs @@ -0,0 +1,38 @@ +using SlnParser.Contracts; +using SlnParser.Contracts.Helper; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace SlnParser.Helper +{ + internal sealed class EnrichSolutionWithSolutionGuid : IEnrichSolution + { + private readonly ISectionParser _sectionParser = new SectionParser(); + + public void Enrich(Solution solution, IEnumerable fileContents) + { + var extensibilityGlobals = _sectionParser.GetFileContentsInGlobalSection( + fileContents, + "ExtensibilityGlobals"); + + solution.Guid = extensibilityGlobals + .Select(ExtractSolutionGuid) + .FirstOrDefault(x => x.HasValue); + } + + private Guid? ExtractSolutionGuid(string line) + { + const string pattern = @"\s*SolutionGuid\s*=\s*{([A-Fa-f0-9\-]+)}"; + var match = Regex.Match(line, pattern); + if (!match.Success) + { + return null; + } + + var guidString = match.Groups[1].Value; + return new Guid(guidString); + } + } +} diff --git a/src/SlnParser/Helper/SectionParser.cs b/src/SlnParser/Helper/SectionParser.cs new file mode 100644 index 0000000..eacb552 --- /dev/null +++ b/src/SlnParser/Helper/SectionParser.cs @@ -0,0 +1,34 @@ +using SlnParser.Contracts.Helper; +using System.Collections.Generic; +using System.Linq; + +namespace SlnParser.Helper +{ + internal class SectionParser : ISectionParser + { + public IEnumerable GetFileContentsInGlobalSection( + IEnumerable fileContents, + string sectionName) + { + var startSection = $"GlobalSection({sectionName}"; + const string endSection = "EndGlobalSection"; + + return GetFileContentsInSection(fileContents, startSection, endSection); + } + + private static IEnumerable GetFileContentsInSection( + IEnumerable fileContents, + string startSection, + string endSection) + { + var section = fileContents + .SkipWhile(line => !line.StartsWith(startSection)) + .TakeWhile(line => !line.StartsWith(endSection)) + .Where(line => !line.StartsWith(startSection)) + .Where(line => !line.StartsWith(endSection)) + .Where(line => !string.IsNullOrWhiteSpace(line)); + + return section.ToList(); + } + } +} diff --git a/src/SlnParser/Helper/SolutionConfigurationPlatformParser.cs b/src/SlnParser/Helper/SolutionConfigurationPlatformParser.cs index 8c6962a..32d8d02 100644 --- a/src/SlnParser/Helper/SolutionConfigurationPlatformParser.cs +++ b/src/SlnParser/Helper/SolutionConfigurationPlatformParser.cs @@ -10,35 +10,21 @@ namespace SlnParser.Helper { internal sealed class SolutionConfigurationPlatformParser : IParseSolutionConfigurationPlatform { + private readonly ISectionParser _sectionParser = new SectionParser(); + public IEnumerable Parse( IEnumerable fileContents, - string startSection) + string sectionName) { if (fileContents == null) throw new ArgumentNullException(nameof(fileContents)); - if (string.IsNullOrWhiteSpace(startSection)) - throw new ArgumentException("Value cannot be null or whitespace.", nameof(startSection)); + if (string.IsNullOrWhiteSpace(sectionName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(sectionName)); - var sectionContents = GetFileContentsInSection(fileContents, startSection); + var sectionContents = _sectionParser.GetFileContentsInGlobalSection(fileContents, sectionName); var projectConfigurationPlatforms = ParseConfigurationPlatforms(sectionContents); return projectConfigurationPlatforms; } - private static IEnumerable GetFileContentsInSection( - IEnumerable fileContents, - string startSection) - { - const string endSection = "EndGlobalSection"; - - var section = fileContents - .SkipWhile(line => !line.StartsWith(startSection)) - .TakeWhile(line => !line.StartsWith(endSection)) - .Where(line => !line.StartsWith(startSection)) - .Where(line => !line.StartsWith(endSection)) - .Where(line => !string.IsNullOrWhiteSpace(line)); - - return section.ToList(); - } - private static IEnumerable ParseConfigurationPlatforms( IEnumerable sectionFileContents) { diff --git a/src/SlnParser/SolutionParser.cs b/src/SlnParser/SolutionParser.cs index ad81b33..60bc5a1 100644 --- a/src/SlnParser/SolutionParser.cs +++ b/src/SlnParser/SolutionParser.cs @@ -28,7 +28,8 @@ public SolutionParser() * because we need the parsed projects before we can map the configurations to them */ new EnrichSolutionWithProjectConfigurationPlatforms(), - new EnrichSolutionWithSolutionFolderFiles() + new EnrichSolutionWithSolutionFolderFiles(), + new EnrichSolutionWithSolutionGuid(), }; }