Skip to content

Commit 1b883e3

Browse files
committed
Initial commit of regex search feature
1 parent f506c9f commit 1b883e3

File tree

8 files changed

+219
-58
lines changed

8 files changed

+219
-58
lines changed

README.md

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,26 @@ This is a config file used by the app to determine what updates to run. It is co
118118
- NpmBuildCommand
119119
- NpmBuildCommand
120120
- String. Npm command to "compile" the npm directory. The CLI command that will be run is: `npm run {{NpmBuildCommand}}`
121+
- RegexSearchOptions
122+
- Optional
123+
- Regex to search for specific string. Handy for finding things you need to manually update, that this tool can't easily do. For example, setting the correct version of .NET in a YAML file for a CI/CD Pipeline
124+
- Required Properties:
125+
- Searches:
126+
- Collection of searches to make in all files that are not ignored
127+
- Object Required Properties:
128+
- Search Regex:
129+
- String. Regex to search for in all files that are not ignored.
130+
- Description:
131+
- String. Description to show in the output.
121132

122133

123134
### Example Options File
124135

125136
```json
137+
{
126138
{
127139
"UpdatePathOptions": {
128-
"RootDirectory": "C:/my-repos/my-app-1",
140+
"RootDirectory": "D:\\GitHub\\ProgrammerAL\\ProgrammerAlSite",
129141
"IgnorePatterns": [
130142
"/samples/",
131143
"\\samples\\"
@@ -159,9 +171,15 @@ This is a config file used by the app to determine what updates to run. It is co
159171
}
160172
},
161173
"NpmOptions": {
162-
"NpmCompileOptions":{
163-
"BuildCommand": "publish"
164-
}
174+
"NpmBuildCommand": "publish"
175+
},
176+
"RegexSearchOptions": {
177+
"Searches": [
178+
{
179+
"SearchRegex": "[0-9]{1,2}\\..+\\.x",
180+
"Description": "YAML Dotnet Version"
181+
}
182+
]
165183
}
166184
}
167185
```

src/CodeUpdater/CodeUpdater/Options/UpdateOptions.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ public class UpdateOptions
2121
/// </summary>
2222
public NpmOptions? NpmOptions { get; set; }
2323

24+
/// <summary>
25+
/// Regex to search for specific string. Handy for finding things you need to manually update, that this tool can't easily do. For example, setting the correct version of .NET in a YAML file for a CI/CD Pipeline
26+
/// </summary>
27+
public RegexSearchOptions? RegexSearchOptions { get; set; }
28+
2429
/// <summary>
2530
/// Options for output logging of the operation
2631
/// </summary>
@@ -185,3 +190,27 @@ public class LoggingOptions
185190
/// </summary>
186191
public string LogLevel { get; set; } = "verbose";
187192
}
193+
194+
public class RegexSearchOptions
195+
{
196+
/// <summary>
197+
/// Collection of searches to make in all files that are not ignored
198+
/// </summary>
199+
[Required]
200+
public required IEnumerable<StringSearch> Searches { get; set; }
201+
202+
public class StringSearch
203+
{
204+
/// <summary>
205+
/// Regex to search for in all files that are not ignored
206+
/// </summary>
207+
[Required(AllowEmptyStrings = false)]
208+
public required string SearchRegex { get; set; }
209+
210+
/// <summary>
211+
/// Description to show in the output
212+
/// </summary>
213+
[Required(AllowEmptyStrings = false)]
214+
public required string Description { get; set; }
215+
}
216+
}

src/CodeUpdater/CodeUpdater/Program.cs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,9 @@ static async ValueTask RunAsync(CommandOptions commandOptions)
3030
var cSharpUpdater = new CSharpUpdater(logger, runProcessHelper, updateOptions);
3131
var npmUpdater = new NpmUpdater(logger, runProcessHelper);
3232
var compileRunner = new CompileRunner(logger, runProcessHelper);
33+
var regexSearcher = new RegexSearcher(logger, updateOptions);
3334

34-
var skipPaths = workLocator.DetermineSkipPaths(updateOptions.UpdatePathOptions.IgnorePatterns);
35-
36-
var updateWork = workLocator.DetermineUpdateWork(updateOptions.UpdatePathOptions.RootDirectory, skipPaths);
35+
var updateWork = workLocator.DetermineUpdateWork(updateOptions.UpdatePathOptions.RootDirectory);
3736

3837
var canRun = await validator.VerifyCanRunAsync(updateWork);
3938

@@ -45,13 +44,14 @@ static async ValueTask RunAsync(CommandOptions commandOptions)
4544

4645
var csUpdates = await cSharpUpdater.UpdateAllCSharpProjectsAsync(updateWork);
4746
var npmUpdates = npmUpdater.UpdateNpmPackages(updateWork);
47+
var searchResults = regexSearcher.SearchUpdatableFiles(updateWork);
4848

4949
//After updating everything, compile all projects
5050
// Don't do this in the above loop in case a project needs an update that would cause it to not compile
5151
// So wait for all projects to be updated
5252
var compileResults = await compileRunner.CompileProjectsAsync(updateWork, updateOptions);
5353

54-
OutputSummary(updateWork, csUpdates, npmUpdates, compileResults, logger);
54+
OutputSummary(updateWork, csUpdates, npmUpdates, compileResults, searchResults, logger);
5555
}
5656

5757
static ILogger SetupLogger(UpdateOptions updateOptions)
@@ -132,7 +132,7 @@ static async Task<UpdateOptions> LoadUpdateOptionsAsync(string configFilePath)
132132
return updateOptions;
133133
}
134134

135-
static void OutputSummary(UpdateWork updateWork, ImmutableArray<CSharpUpdateResult> csUpdates, NpmUpdates npmUpdates, CompileResults compileResults, ILogger logger)
135+
static void OutputSummary(UpdateWork updateWork, ImmutableArray<CSharpUpdateResult> csUpdates, NpmUpdates npmUpdates, CompileResults compileResults, RegexSearchResults searchResults, ILogger logger)
136136
{
137137
#pragma warning disable IDE0058 // Expression value is never used
138138
var builder = new StringBuilder();
@@ -182,6 +182,29 @@ static void OutputSummary(UpdateWork updateWork, ImmutableArray<CSharpUpdateResu
182182
builder.AppendLine($"\t{npmCompileFailure.BuildResult}:{npmCompileFailure.Directory}");
183183
}
184184

185+
builder.AppendLine();
186+
builder.AppendLine("Regex Search Results:");
187+
if (searchResults.Results.Any())
188+
{
189+
var groupedResults = searchResults.Results.GroupBy(x => x.Description);
190+
foreach (var group in groupedResults)
191+
{
192+
builder.AppendLine($"\t{group.Key}:");
193+
foreach (var regexSearchResult in group)
194+
{
195+
builder.AppendLine($"\t\t- {regexSearchResult.FilePath}");
196+
foreach (var matchedString in regexSearchResult.MatchedStrings)
197+
{
198+
builder.AppendLine($"\t\t\t- {matchedString}");
199+
}
200+
}
201+
}
202+
}
203+
else
204+
{
205+
builder.AppendLine("\tNo search results");
206+
}
207+
185208
logger.Information(builder.ToString());
186209
#pragma warning restore IDE0058 // Expression value is never used
187210
}

src/CodeUpdater/CodeUpdater/Properties/launchSettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"profiles": {
33
"CodeUpdater": {
44
"commandName": "Project",
5-
"commandLineArgs": "--config \"../../../sampleUpdateOptions.json"
5+
"commandLineArgs": "--options \"../../../sampleUpdateOptions.json"
66
}
77
}
88
}

src/CodeUpdater/CodeUpdater/Results.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ public record CompileCsProjResult(string CsProjFile, CompileResultType BuildResu
3030
public record CompileNpmDirectoryResults(ImmutableArray<CompileNpmDirectoryResult> Results);
3131
public record CompileNpmDirectoryResult(string Directory, CompileResultType BuildResult);
3232

33+
public record RegexSearchResults(ImmutableArray<RegexSearchResult> Results);
34+
public record RegexSearchResult(string Description, string FilePath, ImmutableArray<string> MatchedStrings);
35+
3336
public enum CompileResultType
3437
{
3538
Success,
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.Immutable;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Text.RegularExpressions;
7+
using System.Threading.Tasks;
8+
9+
using Serilog;
10+
11+
namespace ProgrammerAL.Tools.CodeUpdater.Updaters;
12+
13+
public class RegexSearcher(ILogger Logger, UpdateOptions UpdateOptions)
14+
{
15+
public RegexSearchResults SearchUpdatableFiles(UpdateWork updateWork)
16+
{
17+
if (UpdateOptions.RegexSearchOptions is null)
18+
{
19+
Logger.Information("No RegexSearchOptions config set, will not search files text");
20+
return new RegexSearchResults(ImmutableArray<RegexSearchResult>.Empty);
21+
}
22+
23+
var builder = ImmutableArray.CreateBuilder<RegexSearchResult>();
24+
var allFiles = updateWork.ValidDirectories.SelectMany(x => Directory.GetFiles(x, "*", SearchOption.TopDirectoryOnly));
25+
foreach (var filePath in allFiles)
26+
{
27+
try
28+
{
29+
var fileText = File.ReadAllText(filePath);
30+
foreach (var search in UpdateOptions.RegexSearchOptions.Searches)
31+
{
32+
var matches = Regex.Matches(fileText, search.SearchRegex);
33+
if (matches.Any())
34+
{
35+
var matchedStrings = matches.Select(x => x.Value).ToImmutableArray();
36+
builder.Add(new RegexSearchResult(search.Description, filePath, matchedStrings));
37+
}
38+
}
39+
}
40+
catch (IOException)
41+
{
42+
Logger.Error($"Could not read file '{filePath}' because of an IO Exception. It may be opened by something else. Skipping...");
43+
}
44+
}
45+
46+
return new RegexSearchResults(builder.ToImmutableArray());
47+
}
48+
}

src/CodeUpdater/CodeUpdater/WorkLocator.cs

Lines changed: 79 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -9,93 +9,125 @@
99

1010
namespace ProgrammerAL.Tools.CodeUpdater;
1111

12-
public record UpdateWork(ImmutableArray<string> CsProjectFiles, ImmutableArray<string> NpmDirectories);
12+
public record UpdateWork(ImmutableArray<string> ValidDirectories, ImmutableArray<string> CsProjectFiles, ImmutableArray<string> NpmDirectories);
1313

1414
public class WorkLocator(ILogger Logger, UpdateOptions UpdateOptions)
1515
{
16-
public ImmutableArray<string> DetermineSkipPaths(IEnumerable<string> additionalSkipPaths)
16+
public UpdateWork DetermineUpdateWork(string rootDirectory)
1717
{
18-
var skipPaths = new[]
19-
{
20-
//Ignore all obj and bin folders
21-
@"/obj/Debug/",
22-
@"/obj/Release/",
23-
@"/bin/Debug/",
24-
@"/bin/Release/",
18+
var validDirectories = DetermineValidDirectories(rootDirectory);
19+
var csProjFiles = FindCsProjFiles(validDirectories);
20+
var npmDirectories = FindNpmDirectories(validDirectories);
2521

26-
//Ignore packages inside node_modules
27-
@"/node_modules/"
28-
}
29-
.ToImmutableArray();
30-
31-
//Include the same paths, but with backslashes so this is cross-platform
32-
skipPaths = skipPaths.AddRange(skipPaths.Select(x => x.Replace("/", "\\")));
33-
34-
skipPaths = skipPaths.AddRange(additionalSkipPaths);
35-
36-
return skipPaths;
22+
return new UpdateWork(validDirectories, csProjFiles, npmDirectories);
3723
}
3824

39-
public UpdateWork DetermineUpdateWork(string rootDirectory, ImmutableArray<string> skipPaths)
25+
private ImmutableArray<string> DetermineValidDirectories(string rootDirectory)
4026
{
41-
var csProjFiles = FindCsProjFiles(rootDirectory, skipPaths);
42-
var npmDirectories = FindNpmDirectories(rootDirectory, skipPaths);
27+
var skipPaths = DetermineSkipPaths(UpdateOptions.UpdatePathOptions.IgnorePatterns);
4328

44-
return new UpdateWork(csProjFiles, npmDirectories);
45-
}
46-
47-
public ImmutableArray<string> FindCsProjFiles(string rootDirectory, ImmutableArray<string> skipPaths)
48-
{
49-
var allCsProjFilesPaths = Directory.GetFiles(rootDirectory, "*.csproj", SearchOption.AllDirectories);
50-
var validCsProjFilesPaths = new List<string>();
29+
//Get all directories, including subdirectories
30+
// Make sure the directory path string includes a trailing slash so we can compare it to the skip paths
31+
var allDirectories = Directory.GetDirectories(rootDirectory, "*", SearchOption.AllDirectories)
32+
.Select(x => $"{x}{Path.DirectorySeparatorChar}");
33+
var validDirectories = new List<string>();
5134

52-
foreach (var csProjFilePath in allCsProjFilesPaths)
35+
foreach (var directoryPath in allDirectories)
5336
{
54-
var skipPath = skipPaths.FirstOrDefault(x => csProjFilePath.Contains(x, StringComparison.OrdinalIgnoreCase));
37+
var skipPath = skipPaths.FirstOrDefault(x => directoryPath.Contains(x, StringComparison.OrdinalIgnoreCase));
5538
if (skipPath is object)
5639
{
57-
Logger.Debug($"Skipping '{csProjFilePath}' file because it's path should be ignored by rule: {skipPath}");
40+
Logger.Debug($"Skipping directory '{directoryPath}' because it's path should be ignored by rule: {skipPath}");
5841
}
5942
else
6043
{
61-
validCsProjFilesPaths.Add(csProjFilePath);
44+
validDirectories.Add(directoryPath);
6245
}
6346
}
6447

48+
return validDirectories.ToImmutableArray();
49+
}
50+
51+
public ImmutableArray<string> FindCsProjFiles(ImmutableArray<string> validDirectories)
52+
{
53+
if (UpdateOptions.CSharpOptions is null)
54+
{
55+
Logger.Information("No CSharpOptions config set, will not attempt to update any C# code");
56+
return ImmutableArray<string>.Empty;
57+
}
58+
59+
var validCsProjFilesPaths = new List<string>();
60+
61+
foreach (var dir in validDirectories)
62+
{
63+
var allCsProjFilesPaths = Directory.GetFiles(dir, "*.csproj", SearchOption.TopDirectoryOnly);
64+
validCsProjFilesPaths.AddRange(allCsProjFilesPaths);
65+
}
66+
6567
return validCsProjFilesPaths.ToImmutableArray();
6668
}
6769

68-
public ImmutableArray<string> FindNpmDirectories(string rootDirectory, ImmutableArray<string> skipPaths)
70+
public ImmutableArray<string> FindNpmDirectories(ImmutableArray<string> validDirectories)
6971
{
7072
if (UpdateOptions.NpmOptions is null)
7173
{
7274
Logger.Information("No NpmOptions config set, will not attempt to update NPM Packages");
7375
return ImmutableArray<string>.Empty;
7476
}
7577

76-
var allPackageJsonPaths = Directory.GetFiles(rootDirectory, "package.json", SearchOption.AllDirectories);
7778
var validPaths = new List<string>();
7879

79-
foreach (var packageJsonPath in allPackageJsonPaths)
80+
foreach (var dir in validDirectories)
8081
{
81-
var packagePath = Path.GetDirectoryName(packageJsonPath);
82-
if (string.IsNullOrWhiteSpace(packagePath))
82+
var packageJsonFiles = Directory.GetFiles(dir, "package.json", SearchOption.TopDirectoryOnly);
83+
84+
var packageJsonFile = packageJsonFiles.FirstOrDefault();
85+
86+
if (string.IsNullOrWhiteSpace(packageJsonFile))
8387
{
84-
Logger.Debug($"Skipping '{packageJsonPath}' file because it's path is null or empty");
88+
Logger.Debug($"Skipping directory '{dir}' because it doesn't have a package.json file in it");
8589
continue;
8690
}
8791

88-
var skipPath = skipPaths.FirstOrDefault(x => packagePath.Contains(x, StringComparison.OrdinalIgnoreCase));
89-
if (skipPath is object)
90-
{
91-
Logger.Debug($"Skipping '{packagePath}' because it's path should be ignored by rule: {skipPath}");
92-
}
93-
else
92+
var packagePath = Path.GetDirectoryName(packageJsonFile);
93+
if (string.IsNullOrWhiteSpace(packagePath))
9494
{
95-
validPaths.Add(packagePath);
95+
Logger.Debug($"Skipping '{packageJsonFile}' file because it's package.json path is null or empty");
96+
continue;
9697
}
98+
99+
validPaths.Add(packagePath);
97100
}
98101

99102
return validPaths.ToImmutableArray();
100103
}
104+
105+
private ImmutableArray<string> DetermineSkipPaths(IEnumerable<string> additionalSkipPaths)
106+
{
107+
var skipPaths = new[]
108+
{
109+
//Ignore all obj and bin folders
110+
@"/obj/",
111+
@"/obj/Debug/",
112+
@"/obj/Release/",
113+
@"/bin/",
114+
@"/bin/Debug/",
115+
@"/bin/Release/",
116+
117+
//Ignore packages inside node_modules
118+
@"/node_modules/",
119+
120+
//Ignore the .git folder
121+
@"/.git/",
122+
@"/.vs/",
123+
}
124+
.ToImmutableArray();
125+
126+
//Include the same paths, but with backslashes so this is cross-platform
127+
skipPaths = skipPaths.AddRange(skipPaths.Select(x => x.Replace("/", "\\")));
128+
129+
skipPaths = skipPaths.AddRange(additionalSkipPaths);
130+
131+
return skipPaths;
132+
}
101133
}

0 commit comments

Comments
 (0)