Skip to content

Commit

Permalink
Merge branch 'find-sln-file' into main
Browse files Browse the repository at this point in the history
Fixes #19
  • Loading branch information
timabell committed Nov 20, 2024
2 parents cdeb2fd + 5fe6dda commit 12cdbda
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 11 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Files and folders will be recursively add/removed to match the filesystem.

`SolutionItems` folder will be created and populated if missing. The name can be customized with `--folder`.

If there is only one `.sln` file in the current folder then the `--solution` argument can be omitted and it will automatically be found.

## License

- [A-GPL v3](LICENSE)
Expand Down
7 changes: 5 additions & 2 deletions src/CLI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ public CLI(IGuidGenerator guidGenerator)

private class Options
{
[Option('s', "solution", Required = true, HelpText = "path to .sln file to modify")]
public required string SlnPath { get; set; }
[Option('s', "solution", HelpText = "path to .sln file to modify")]
public string? SlnPath { get; set; }

[Option('f', "folder", Required = false, HelpText = "Solution folder to target")]
public string SlnFolder { get; set; } = "SolutionItems";
Expand Down Expand Up @@ -61,6 +61,9 @@ public int Run(string[] args)
Console.Error.WriteLine();
WriteUsage();
return 5;
} catch(MultipleSlnFilesFoundException ex) {
Console.Error.WriteLine(ex.Message);
return 6;
}

Console.Out.WriteLine("Sync completed.");
Expand Down
24 changes: 23 additions & 1 deletion src/SlnSync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ public SlnSync() : this(new DefaultGuidGenerator())
/// <param name="slnPath">relative path to sln file to modify</param>
/// <param name="slnFolder"></param>
/// <param name="paths">list of paths to recursively add/update SolutionItems virtual folders with</param>
public void SyncSlnFile(string slnPath, string slnFolder, IEnumerable<string> paths)
public void SyncSlnFile(string? slnPath, string slnFolder, IEnumerable<string> paths)
{
if (slnPath is null)
{
slnPath = FindSlnFile();
}
if (!slnPath.EndsWith(".sln"))
{
throw new InvalidSlnPathException(slnPath);
Expand Down Expand Up @@ -106,6 +110,23 @@ private void SyncFolder(SolutionFolder parentFolder, DirectoryInfo directory, st
}
}

/// <summary>
/// search current folder for single .sln file and return it
/// throw if not found or more than one found
/// </summary>
/// <returns>sln file if found</returns>
/// <exception cref="SlnFileNotFoundException"></exception>
private static string FindSlnFile()
{
var slnFiles = Directory.GetFiles(Directory.GetCurrentDirectory(), "*.sln");
return slnFiles.Length switch
{
0 => throw new SlnFileNotFoundException("No .sln files found in current directory."),
> 1 => throw new MultipleSlnFilesFoundException("Multiple .sln files found in current directory. Specify one with --solution"),
_ => slnFiles[0],
};
}

private SolutionFolder FindOrCreateSolutionFolder(ICollection<IProject> solutionProjects,
string solutionFolderName)
{
Expand All @@ -126,6 +147,7 @@ private SolutionFolder FindOrCreateSolutionFolder(ICollection<IProject> solution
}

public class PathNotFoundException(string path) : Exception($"Path not found: '{path}'");
public class MultipleSlnFilesFoundException(string message) : Exception(message: message);
public class SlnFileNotFoundException(string path) : Exception($"Invalid .sln file '{path}' - File not found.");
public class InvalidSlnPathException(string path) : Exception($"Invalid .sln file '{path}' - File must have .sln extension");
public class EmptyPathListException() : Exception();
95 changes: 87 additions & 8 deletions tests/IntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class IntegrationTests
private const string TargetSlnFile = "target.sln";
private readonly CLI _cli;
private readonly string _testFolder;
private const int SuccessExitCode = 0;

private static readonly string[] GuidsToReturn =
[
Expand Down Expand Up @@ -58,7 +59,8 @@ public void AddsToBlankSln()
});

// Act
_cli.Run(["-s", TargetSlnFile, "root-file.txt", "subfolder"]);
_cli.Run(["-s", TargetSlnFile, "root-file.txt", "subfolder"])
.Should().Be(SuccessExitCode, because: "command should succeed");

// Assert
const string expected = @"
Expand Down Expand Up @@ -143,7 +145,8 @@ public void IgnoresExistingItems()
});

// Act
_cli.Run(["-s", TargetSlnFile, "root-file.txt", "subfolder"]);
_cli.Run(["-s", TargetSlnFile, "root-file.txt", "subfolder"])
.Should().Be(SuccessExitCode, because: "command should succeed");

// Assert
const string expected = @"
Expand Down Expand Up @@ -229,7 +232,8 @@ public void RemovesMissingFiles()
});

// Act
_cli.Run(["-s", TargetSlnFile, "root-file.txt", "subfolder"]);
_cli.Run(["-s", TargetSlnFile, "root-file.txt", "subfolder"])
.Should().Be(SuccessExitCode, because: "command should succeed");

// Assert
const string expected = @"
Expand Down Expand Up @@ -298,7 +302,8 @@ public void CustomFolderName()
});

// Act
_cli.Run(["-s", TargetSlnFile, "-f", "My Items", "root-file.txt", "subfolder"]);
_cli.Run(["-s", TargetSlnFile, "-f", "My Items", "root-file.txt", "subfolder"])
.Should().Be(SuccessExitCode, because: "command should succeed");

// Assert
const string expected = @"
Expand Down Expand Up @@ -366,7 +371,8 @@ public void PathSanitization()
});

// Act
_cli.Run(["-s", TargetSlnFile, "root-file.txt", "subfolder/"]);
_cli.Run(["-s", TargetSlnFile, "root-file.txt", "subfolder/"])
.Should().Be(SuccessExitCode, because: "command should succeed");

// Assert
const string expected = @"
Expand Down Expand Up @@ -431,7 +437,8 @@ public void AddsBOM()
});

// Act
_cli.Run(["-s", TargetSlnFile, "some-file.txt"]);
_cli.Run(["-s", TargetSlnFile, "some-file.txt"])
.Should().Be(SuccessExitCode, because: "command should succeed");

File.ReadAllText(Path.Combine(_testFolder, TargetSlnFile));

Expand Down Expand Up @@ -489,7 +496,8 @@ public void RemovesEmptyFolder()
});

// Act
_cli.Run(["-s", TargetSlnFile, "subfolder"]);
_cli.Run(["-s", TargetSlnFile, "subfolder"])
.Should().Be(SuccessExitCode, because: "command should succeed");

// Assert
const string expected = @"
Expand Down Expand Up @@ -526,6 +534,75 @@ public void RemovesEmptyFolder()
ModifiedSln().Should().Be(expected);
}


[Fact]
public void FindsSlnFile()
{
// Arrange
SetupSln(@"
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
");

SetupFilesystem(new[]
{
"a-file.txt",
});

// Act
_cli.Run(["a-file.txt"])
.Should().Be(SuccessExitCode, because: "command should succeed");

// Assert
const string expected = @"
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""SolutionItems"", ""SolutionItems"", ""{17591C35-3F90-4F4A-AA13-45CF8D824066}""
ProjectSection(SolutionItems) = preProject
a-file.txt = a-file.txt
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
";

File.WriteAllText(Path.Combine(_testFolder, "expected.sln"), expected); // for kdiff debugging

ModifiedSln().Should().Be(expected);
}

[Fact]
public void FindsSlnFileInSameFolder()
{
File.WriteAllText(Path.Combine(_testFolder, "solution1.sln"), "blank");
File.WriteAllText(Path.Combine(_testFolder, "solution2.sln"), "blank");
_cli.Run(["some-file.txt"])
.Should().Be(6, because: "two solution files exist and arguments didn't specify which to use");
// todo: assert message written to stderr
}

private string ModifiedSln() => File.ReadAllText(Path.Combine(_testFolder, TargetSlnFile));

private void SetupFilesystem(IEnumerable<string> paths)
Expand All @@ -549,7 +626,9 @@ private void SetupFile(string path)

private void SetupSln(string slnContents)
{
File.WriteAllText(Path.Combine(_testFolder, "original.sln"), slnContents);
var originalSlnFolder = Path.Combine(_testFolder, "original"); // put in subfolder to avoid sln detection finding it
Directory.CreateDirectory(originalSlnFolder);
File.WriteAllText(Path.Combine(originalSlnFolder, "original.sln"), slnContents);
File.WriteAllText(Path.Combine(_testFolder, TargetSlnFile), slnContents);
}
}

0 comments on commit 12cdbda

Please sign in to comment.