diff --git a/README.md b/README.md
index 85709c0..3481b22 100644
--- a/README.md
+++ b/README.md
@@ -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)
diff --git a/src/CLI.cs b/src/CLI.cs
index 9f30a46..0ff8157 100644
--- a/src/CLI.cs
+++ b/src/CLI.cs
@@ -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";
@@ -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.");
diff --git a/src/SlnSync.cs b/src/SlnSync.cs
index 42dacff..e01f088 100644
--- a/src/SlnSync.cs
+++ b/src/SlnSync.cs
@@ -18,8 +18,12 @@ public SlnSync() : this(new DefaultGuidGenerator())
/// relative path to sln file to modify
///
/// list of paths to recursively add/update SolutionItems virtual folders with
- public void SyncSlnFile(string slnPath, string slnFolder, IEnumerable paths)
+ public void SyncSlnFile(string? slnPath, string slnFolder, IEnumerable paths)
{
+ if (slnPath is null)
+ {
+ slnPath = FindSlnFile();
+ }
if (!slnPath.EndsWith(".sln"))
{
throw new InvalidSlnPathException(slnPath);
@@ -106,6 +110,23 @@ private void SyncFolder(SolutionFolder parentFolder, DirectoryInfo directory, st
}
}
+ ///
+ /// search current folder for single .sln file and return it
+ /// throw if not found or more than one found
+ ///
+ /// sln file if found
+ ///
+ 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 solutionProjects,
string solutionFolderName)
{
@@ -126,6 +147,7 @@ private SolutionFolder FindOrCreateSolutionFolder(ICollection 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();
diff --git a/tests/IntegrationTests.cs b/tests/IntegrationTests.cs
index f9ccbf4..3f5fe5a 100644
--- a/tests/IntegrationTests.cs
+++ b/tests/IntegrationTests.cs
@@ -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 =
[
@@ -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 = @"
@@ -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 = @"
@@ -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 = @"
@@ -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 = @"
@@ -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 = @"
@@ -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));
@@ -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 = @"
@@ -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 paths)
@@ -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);
}
}