Skip to content

Commit 50acbe2

Browse files
CopiloteNeRGy164
andcommitted
Complete folder and glob pattern support with documentation
Co-authored-by: eNeRGy164 <[email protected]>
1 parent b5b0844 commit 50acbe2

File tree

5 files changed

+98
-30
lines changed

5 files changed

+98
-30
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,20 @@ dotnet tool install --global DendroDocs.Tool
2828
Example usage:
2929

3030
```shell
31+
# Analyze a solution file
3132
dendrodocs-analyze --solution G:\DendroDocs\dotnet-shared-lib\DendroDocs.Shared.sln --output shared.json --pretty --verbose --exclude G:\DendroDocs\dotnet-shared-lib\build\_build.csproj
33+
34+
# Analyze a single project file
35+
dendrodocs-analyze --project MyProject.csproj --output project.json --pretty
36+
37+
# Analyze all projects in a folder
38+
dendrodocs-analyze --folder /path/to/projects --output folder.json --pretty
39+
40+
# Use glob patterns to select specific projects
41+
dendrodocs-analyze --folder "src/**/*.csproj" --output matched.json --pretty
42+
43+
# Exclude specific projects when analyzing a folder
44+
dendrodocs-analyze --folder /path/to/projects --exclude /path/to/unwanted.csproj,/path/to/test.csproj --output filtered.json
3245
```
3346

3447
## Output

src/DendroDocs.Tool/AnalyzerSetup.cs

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -86,59 +86,70 @@ private static IEnumerable<string> DiscoverProjectFiles(string folderPathOrPatte
8686
return Directory.GetFiles(folderPathOrPattern, "*.csproj", SearchOption.AllDirectories);
8787
}
8888

89-
// Treat as a glob pattern
89+
// Handle glob patterns
9090
var matcher = new Matcher();
9191

92-
// If the pattern doesn't contain wildcards, assume it's a folder and add /**/*.csproj
92+
// If the pattern doesn't contain wildcards, assume it's a folder that doesn't exist
9393
if (!folderPathOrPattern.Contains('*') && !folderPathOrPattern.Contains('?'))
9494
{
95-
var basePath = folderPathOrPattern;
96-
if (!basePath.EndsWith(Path.DirectorySeparatorChar))
97-
{
98-
basePath += Path.DirectorySeparatorChar;
99-
}
100-
matcher.AddInclude($"**/*.csproj");
101-
102-
// Use current directory as base if path doesn't exist as a directory
103-
var baseDirectory = Directory.Exists(folderPathOrPattern) ? folderPathOrPattern : Directory.GetCurrentDirectory();
104-
var result = matcher.Execute(new DirectoryInfoWrapper(new DirectoryInfo(baseDirectory)));
105-
return result.Files.Select(f => Path.Combine(baseDirectory, f.Path));
95+
throw new DirectoryNotFoundException($"Folder not found: {folderPathOrPattern}");
96+
}
97+
98+
// Handle as a glob pattern
99+
string baseDirectory;
100+
string pattern;
101+
102+
// Check if pattern is absolute or relative
103+
if (Path.IsPathRooted(folderPathOrPattern))
104+
{
105+
// Absolute path - extract base directory and relative pattern
106+
var parts = SplitAbsolutePattern(folderPathOrPattern);
107+
baseDirectory = parts.BaseDirectory;
108+
pattern = parts.Pattern;
106109
}
107110
else
108111
{
109-
// Handle as a true glob pattern
110-
matcher.AddInclude(folderPathOrPattern);
111-
112-
// Determine base directory from the pattern
113-
var baseDirectory = GetBasDirectoryFromPattern(folderPathOrPattern);
114-
var result = matcher.Execute(new DirectoryInfoWrapper(new DirectoryInfo(baseDirectory)));
115-
return result.Files.Select(f => Path.Combine(baseDirectory, f.Path));
112+
// Relative path
113+
baseDirectory = Directory.GetCurrentDirectory();
114+
pattern = folderPathOrPattern;
116115
}
116+
117+
matcher.AddInclude(pattern);
118+
var result = matcher.Execute(new DirectoryInfoWrapper(new DirectoryInfo(baseDirectory)));
119+
return result.Files.Select(f => Path.Combine(baseDirectory, f.Path));
117120
}
118121

119-
private static string GetBasDirectoryFromPattern(string pattern)
122+
private static (string BaseDirectory, string Pattern) SplitAbsolutePattern(string absolutePattern)
120123
{
121-
// Find the first occurrence of wildcards and take the directory part before it
124+
// Find the first wildcard
122125
var wildcardIndex = Math.Min(
123-
pattern.IndexOf('*') >= 0 ? pattern.IndexOf('*') : int.MaxValue,
124-
pattern.IndexOf('?') >= 0 ? pattern.IndexOf('?') : int.MaxValue
126+
absolutePattern.IndexOf('*') >= 0 ? absolutePattern.IndexOf('*') : int.MaxValue,
127+
absolutePattern.IndexOf('?') >= 0 ? absolutePattern.IndexOf('?') : int.MaxValue
125128
);
126129

127130
if (wildcardIndex == int.MaxValue)
128131
{
129-
// No wildcards, use the pattern as is if it's a directory
130-
return Directory.Exists(pattern) ? pattern : Directory.GetCurrentDirectory();
132+
// No wildcards found, treat as directory
133+
return (absolutePattern, "**/*.csproj");
131134
}
132135

133-
var basePart = pattern.Substring(0, wildcardIndex);
136+
// Find the last directory separator before the wildcard
137+
var basePart = absolutePattern.Substring(0, wildcardIndex);
134138
var lastSeparator = basePart.LastIndexOf(Path.DirectorySeparatorChar);
135139

136140
if (lastSeparator >= 0)
137141
{
138142
var baseDir = basePart.Substring(0, lastSeparator);
139-
return Directory.Exists(baseDir) ? baseDir : Directory.GetCurrentDirectory();
143+
var relativePattern = absolutePattern.Substring(lastSeparator + 1);
144+
145+
// Ensure base directory exists
146+
if (Directory.Exists(baseDir))
147+
{
148+
return (baseDir, relativePattern);
149+
}
140150
}
141151

142-
return Directory.GetCurrentDirectory();
152+
// Fallback to current directory
153+
return (Directory.GetCurrentDirectory(), absolutePattern);
143154
}
144155
}

src/DendroDocs.Tool/Options.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public class Options
1010
[Option("project", Required = true, SetName = "project", HelpText = "The project to analyze.")]
1111
public string? ProjectPath { get; set; }
1212

13-
[Option("folder", Required = true, SetName = "folder", HelpText = "The folder to search for projects, or a glob pattern to match project files.")]
13+
[Option("folder", Required = true, SetName = "folder", HelpText = "The folder to search for projects recursively, or a glob pattern to match specific project files (e.g., 'src/**/*.csproj').")]
1414
public string? FolderPath { get; set; }
1515

1616
[Option("exclude", Required = false, Separator = ',', HelpText = "Any projects to exclude from analysis.")]

src/DendroDocs.Tool/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,20 @@ dotnet tool install --global DendroDocs.Tool
2222
## Example usage
2323

2424
```shell
25+
# Analyze a solution file
2526
dendrodocs-analyze --solution G:\DendroDocs\dotnet-shared-lib\DendroDocs.Shared.sln --output shared.json --pretty --verbose --exclude G:\DendroDocs\dotnet-shared-lib\build\_build.csproj
27+
28+
# Analyze a single project file
29+
dendrodocs-analyze --project MyProject.csproj --output project.json --pretty
30+
31+
# Analyze all projects in a folder
32+
dendrodocs-analyze --folder /path/to/projects --output folder.json --pretty
33+
34+
# Use glob patterns to select specific projects
35+
dendrodocs-analyze --folder "src/**/*.csproj" --output matched.json --pretty
36+
37+
# Exclude specific projects when analyzing a folder
38+
dendrodocs-analyze --folder /path/to/projects --exclude /path/to/unwanted.csproj,/path/to/test.csproj --output filtered.json
2639
```
2740

2841
## Output

tests/DendroDocs.Tool.Tests/AnalyzerSetup/AnalyzerSetupTests.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,37 @@ public void FolderShouldFilterExcludedProjects()
147147
analyzerSetup.Projects.ShouldAllBe(p => p.FilePath != null && p.FilePath.EndsWith("Project.csproj"));
148148
}
149149

150+
[TestMethod]
151+
public void GlobPatternShouldFindMatchingProjects()
152+
{
153+
// Arrange
154+
var basePath = GetBasePath();
155+
var originalDir = Directory.GetCurrentDirectory();
156+
157+
try
158+
{
159+
// Change to the test base directory to make relative patterns work
160+
Directory.SetCurrentDirectory(basePath);
161+
var globPattern = "AnalyzerSetupVerification/**/*.csproj";
162+
163+
// Act
164+
using var analyzerSetup = AnalyzerSetup.BuildFolderAnalyzer(globPattern);
165+
166+
// Assert
167+
// Should find the 3 non-test projects that match the pattern (excludes TestProject due to test references)
168+
analyzerSetup.Projects.Count().ShouldBe(3);
169+
170+
var projectPaths = analyzerSetup.Projects.Select(p => p.FilePath).ToList();
171+
projectPaths.ShouldContain(path => path != null && path.EndsWith("Project.csproj"));
172+
projectPaths.ShouldContain(path => path != null && path.EndsWith("OtherProject.csproj"));
173+
projectPaths.ShouldContain(path => path != null && path.EndsWith("AnotherProject.csproj"));
174+
}
175+
finally
176+
{
177+
Directory.SetCurrentDirectory(originalDir);
178+
}
179+
}
180+
150181
private static string GetSolutionPath()
151182
{
152183
var currentDirectory = Directory.GetCurrentDirectory().AsSpan();

0 commit comments

Comments
 (0)