diff --git a/TextReplacement.sln b/TextReplacement.sln new file mode 100644 index 0000000..d2a07f1 --- /dev/null +++ b/TextReplacement.sln @@ -0,0 +1,47 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# 17 +VisualStudioVersion = 17.9.34714.143 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D0CBCAD4-B7D5-41FA-9B13-E3446E1D7A53}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TextReplacementApp", "TextReplacementApp\TextReplacementApp.csproj", "{A4F06817-13B0-4937-ADE2-C34F8038BCD1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TextReplacementConsoleApp", "TextReplacementConsoleApp\TextReplacementConsoleApp.csproj", "{69A9ABD0-BED3-4370-9CBF-55F6DF36CDD7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TextReplacementShared", "TextReplacementShared\TextReplacementShared.csproj", "{94CD1765-7876-4928-8B37-87300FFA66E2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TextReplacementConsoleDemos", "TextReplacementConsoleDemos\TextReplacementConsoleDemos.csproj", "{F7B2007A-EB8E-4205-84B9-5D96A7F5119E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A4F06817-13B0-4937-ADE2-C34F8038BCD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4F06817-13B0-4937-ADE2-C34F8038BCD1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4F06817-13B0-4937-ADE2-C34F8038BCD1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4F06817-13B0-4937-ADE2-C34F8038BCD1}.Release|Any CPU.Build.0 = Release|Any CPU + {69A9ABD0-BED3-4370-9CBF-55F6DF36CDD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69A9ABD0-BED3-4370-9CBF-55F6DF36CDD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69A9ABD0-BED3-4370-9CBF-55F6DF36CDD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69A9ABD0-BED3-4370-9CBF-55F6DF36CDD7}.Release|Any CPU.Build.0 = Release|Any CPU + {94CD1765-7876-4928-8B37-87300FFA66E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94CD1765-7876-4928-8B37-87300FFA66E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94CD1765-7876-4928-8B37-87300FFA66E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94CD1765-7876-4928-8B37-87300FFA66E2}.Release|Any CPU.Build.0 = Release|Any CPU + {F7B2007A-EB8E-4205-84B9-5D96A7F5119E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7B2007A-EB8E-4205-84B9-5D96A7F5119E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7B2007A-EB8E-4205-84B9-5D96A7F5119E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7B2007A-EB8E-4205-84B9-5D96A7F5119E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {72FFA8BA-53B0-4F29-A6D3-96DE7FF8EDFA} + EndGlobalSection +EndGlobal diff --git a/TextReplacementApp.sln b/TextReplacementApp.sln deleted file mode 100644 index 4d8729f..0000000 --- a/TextReplacementApp.sln +++ /dev/null @@ -1,146 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.9.34714.143 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TextReplacementApp", "TextReplacementApp\TextReplacementApp.csproj", "{A4F06817-13B0-4937-ADE2-C34F8038BCD1}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D0CBCAD4-B7D5-41FA-9B13-E3446E1D7A53}" - ProjectSection(SolutionItems) = preProject - README.md = README.md - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TextReplacementConsoleApp", "TextReplacementConsoleApp\TextReplacementConsoleApp.csproj", "{69A9ABD0-BED3-4370-9CBF-55F6DF36CDD7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xyLOGIX.Core.Debug", "..\xyLOGIX.Core.Debug.VS2019\xyLOGIX.Core.Debug\xyLOGIX.Core.Debug.csproj", "{C445C052-26B6-4884-9A7B-25DC7ABC0DED}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xyLOGIX.Collections.Synchronized", "..\xyLOGIX.Collections.Synchronized.VS2019\xyLOGIX.Collections.Synchronized\xyLOGIX.Collections.Synchronized.csproj", "{33FD8195-DDE4-4245-9EDA-35836CAA40C5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xyLOGIX.Core.Extensions", "..\xyLOGIX.Core.Extensions.VS2019\xyLOGIX.Core.Extensions\xyLOGIX.Core.Extensions.csproj", "{5528D7FB-B88B-4E9D-9BBD-B6B6B893666A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xyLOGIX.Core.Extensions.Providers", "..\xyLOGIX.Core.Extensions.VS2019\xyLOGIX.Core.Extensions.Providers\xyLOGIX.Core.Extensions.Providers.csproj", "{EA03D76A-0A2F-4BDE-9690-92F82AF551D7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xyLOGIX.Core.Extensions.Providers.Factories", "..\xyLOGIX.Core.Extensions.VS2019\xyLOGIX.Core.Extensions.Providers.Factories\xyLOGIX.Core.Extensions.Providers.Factories.csproj", "{EA3A9AB0-6AFD-456D-BB58-F50E5C8001DD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xyLOGIX.Core.Extensions.Providers.Interfaces", "..\xyLOGIX.Core.Extensions.VS2019\xyLOGIX.Core.Extensions.Providers.Interfaces\xyLOGIX.Core.Extensions.Providers.Interfaces.csproj", "{1FF580E3-D9C2-48B7-917E-53C340BF16EC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xyLOGIX.Core.Assemblies.Info", "..\xyLOGIX.Core.Assemblies.Info.VS2019\xyLOGIX.Core.Assemblies.Info\xyLOGIX.Core.Assemblies.Info.csproj", "{1611C59B-1C1E-4F7C-A1CC-7F263DD31188}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xyLOGIX.Files.MemoryMapped.Interfaces", "..\xyLOGIX.Files.MemoryMapped.VS2022\xyLOGIX.Files.MemoryMapped.Interfaces\xyLOGIX.Files.MemoryMapped.Interfaces.csproj", "{C7CB816D-0FB7-4C62-A225-98711E5537A5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xyLOGIX.Files.MemoryMapped.Factories", "..\xyLOGIX.Files.MemoryMapped.VS2022\xyLOGIX.Files.MemoryMapped.Factories\xyLOGIX.Files.MemoryMapped.Factories.csproj", "{7B30B65B-220C-49AC-9448-C190F8F71715}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xyLOGIX.Files.MemoryMapped", "..\xyLOGIX.Files.MemoryMapped.VS2022\xyLOGIX.Files.MemoryMapped\xyLOGIX.Files.MemoryMapped.csproj", "{2557ED07-457A-415C-9C36-25C5ED163907}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A4F06817-13B0-4937-ADE2-C34F8038BCD1}.Debug|x64.ActiveCfg = Debug|x64 - {A4F06817-13B0-4937-ADE2-C34F8038BCD1}.Debug|x64.Build.0 = Debug|x64 - {A4F06817-13B0-4937-ADE2-C34F8038BCD1}.Debug|x86.ActiveCfg = Debug|x86 - {A4F06817-13B0-4937-ADE2-C34F8038BCD1}.Debug|x86.Build.0 = Debug|x86 - {A4F06817-13B0-4937-ADE2-C34F8038BCD1}.Release|x64.ActiveCfg = Release|x64 - {A4F06817-13B0-4937-ADE2-C34F8038BCD1}.Release|x64.Build.0 = Release|x64 - {A4F06817-13B0-4937-ADE2-C34F8038BCD1}.Release|x86.ActiveCfg = Release|x86 - {A4F06817-13B0-4937-ADE2-C34F8038BCD1}.Release|x86.Build.0 = Release|x86 - {69A9ABD0-BED3-4370-9CBF-55F6DF36CDD7}.Debug|x64.ActiveCfg = Debug|x64 - {69A9ABD0-BED3-4370-9CBF-55F6DF36CDD7}.Debug|x64.Build.0 = Debug|x64 - {69A9ABD0-BED3-4370-9CBF-55F6DF36CDD7}.Debug|x86.ActiveCfg = Debug|x86 - {69A9ABD0-BED3-4370-9CBF-55F6DF36CDD7}.Debug|x86.Build.0 = Debug|x86 - {69A9ABD0-BED3-4370-9CBF-55F6DF36CDD7}.Release|x64.ActiveCfg = Release|x64 - {69A9ABD0-BED3-4370-9CBF-55F6DF36CDD7}.Release|x64.Build.0 = Release|x64 - {69A9ABD0-BED3-4370-9CBF-55F6DF36CDD7}.Release|x86.ActiveCfg = Release|x86 - {69A9ABD0-BED3-4370-9CBF-55F6DF36CDD7}.Release|x86.Build.0 = Release|x86 - {C445C052-26B6-4884-9A7B-25DC7ABC0DED}.Debug|x64.ActiveCfg = Debug|x64 - {C445C052-26B6-4884-9A7B-25DC7ABC0DED}.Debug|x64.Build.0 = Debug|x64 - {C445C052-26B6-4884-9A7B-25DC7ABC0DED}.Debug|x86.ActiveCfg = Debug|x86 - {C445C052-26B6-4884-9A7B-25DC7ABC0DED}.Debug|x86.Build.0 = Debug|x86 - {C445C052-26B6-4884-9A7B-25DC7ABC0DED}.Release|x64.ActiveCfg = Release|x64 - {C445C052-26B6-4884-9A7B-25DC7ABC0DED}.Release|x64.Build.0 = Release|x64 - {C445C052-26B6-4884-9A7B-25DC7ABC0DED}.Release|x86.ActiveCfg = Release|x86 - {C445C052-26B6-4884-9A7B-25DC7ABC0DED}.Release|x86.Build.0 = Release|x86 - {33FD8195-DDE4-4245-9EDA-35836CAA40C5}.Debug|x64.ActiveCfg = Debug|x64 - {33FD8195-DDE4-4245-9EDA-35836CAA40C5}.Debug|x64.Build.0 = Debug|x64 - {33FD8195-DDE4-4245-9EDA-35836CAA40C5}.Debug|x86.ActiveCfg = Debug|x86 - {33FD8195-DDE4-4245-9EDA-35836CAA40C5}.Debug|x86.Build.0 = Debug|x86 - {33FD8195-DDE4-4245-9EDA-35836CAA40C5}.Release|x64.ActiveCfg = Release|x64 - {33FD8195-DDE4-4245-9EDA-35836CAA40C5}.Release|x64.Build.0 = Release|x64 - {33FD8195-DDE4-4245-9EDA-35836CAA40C5}.Release|x86.ActiveCfg = Release|x86 - {33FD8195-DDE4-4245-9EDA-35836CAA40C5}.Release|x86.Build.0 = Release|x86 - {5528D7FB-B88B-4E9D-9BBD-B6B6B893666A}.Debug|x64.ActiveCfg = Debug|x64 - {5528D7FB-B88B-4E9D-9BBD-B6B6B893666A}.Debug|x64.Build.0 = Debug|x64 - {5528D7FB-B88B-4E9D-9BBD-B6B6B893666A}.Debug|x86.ActiveCfg = Debug|x86 - {5528D7FB-B88B-4E9D-9BBD-B6B6B893666A}.Debug|x86.Build.0 = Debug|x86 - {5528D7FB-B88B-4E9D-9BBD-B6B6B893666A}.Release|x64.ActiveCfg = Release|x64 - {5528D7FB-B88B-4E9D-9BBD-B6B6B893666A}.Release|x64.Build.0 = Release|x64 - {5528D7FB-B88B-4E9D-9BBD-B6B6B893666A}.Release|x86.ActiveCfg = Release|x86 - {5528D7FB-B88B-4E9D-9BBD-B6B6B893666A}.Release|x86.Build.0 = Release|x86 - {EA03D76A-0A2F-4BDE-9690-92F82AF551D7}.Debug|x64.ActiveCfg = Debug|x64 - {EA03D76A-0A2F-4BDE-9690-92F82AF551D7}.Debug|x64.Build.0 = Debug|x64 - {EA03D76A-0A2F-4BDE-9690-92F82AF551D7}.Debug|x86.ActiveCfg = Debug|x86 - {EA03D76A-0A2F-4BDE-9690-92F82AF551D7}.Debug|x86.Build.0 = Debug|x86 - {EA03D76A-0A2F-4BDE-9690-92F82AF551D7}.Release|x64.ActiveCfg = Release|x64 - {EA03D76A-0A2F-4BDE-9690-92F82AF551D7}.Release|x64.Build.0 = Release|x64 - {EA03D76A-0A2F-4BDE-9690-92F82AF551D7}.Release|x86.ActiveCfg = Release|x86 - {EA03D76A-0A2F-4BDE-9690-92F82AF551D7}.Release|x86.Build.0 = Release|x86 - {EA3A9AB0-6AFD-456D-BB58-F50E5C8001DD}.Debug|x64.ActiveCfg = Debug|x64 - {EA3A9AB0-6AFD-456D-BB58-F50E5C8001DD}.Debug|x64.Build.0 = Debug|x64 - {EA3A9AB0-6AFD-456D-BB58-F50E5C8001DD}.Debug|x86.ActiveCfg = Debug|x86 - {EA3A9AB0-6AFD-456D-BB58-F50E5C8001DD}.Debug|x86.Build.0 = Debug|x86 - {EA3A9AB0-6AFD-456D-BB58-F50E5C8001DD}.Release|x64.ActiveCfg = Release|x64 - {EA3A9AB0-6AFD-456D-BB58-F50E5C8001DD}.Release|x64.Build.0 = Release|x64 - {EA3A9AB0-6AFD-456D-BB58-F50E5C8001DD}.Release|x86.ActiveCfg = Release|x86 - {EA3A9AB0-6AFD-456D-BB58-F50E5C8001DD}.Release|x86.Build.0 = Release|x86 - {1FF580E3-D9C2-48B7-917E-53C340BF16EC}.Debug|x64.ActiveCfg = Debug|x64 - {1FF580E3-D9C2-48B7-917E-53C340BF16EC}.Debug|x64.Build.0 = Debug|x64 - {1FF580E3-D9C2-48B7-917E-53C340BF16EC}.Debug|x86.ActiveCfg = Debug|x86 - {1FF580E3-D9C2-48B7-917E-53C340BF16EC}.Debug|x86.Build.0 = Debug|x86 - {1FF580E3-D9C2-48B7-917E-53C340BF16EC}.Release|x64.ActiveCfg = Release|x64 - {1FF580E3-D9C2-48B7-917E-53C340BF16EC}.Release|x64.Build.0 = Release|x64 - {1FF580E3-D9C2-48B7-917E-53C340BF16EC}.Release|x86.ActiveCfg = Release|x86 - {1FF580E3-D9C2-48B7-917E-53C340BF16EC}.Release|x86.Build.0 = Release|x86 - {1611C59B-1C1E-4F7C-A1CC-7F263DD31188}.Debug|x64.ActiveCfg = Debug|x64 - {1611C59B-1C1E-4F7C-A1CC-7F263DD31188}.Debug|x64.Build.0 = Debug|x64 - {1611C59B-1C1E-4F7C-A1CC-7F263DD31188}.Debug|x86.ActiveCfg = Debug|x86 - {1611C59B-1C1E-4F7C-A1CC-7F263DD31188}.Debug|x86.Build.0 = Debug|x86 - {1611C59B-1C1E-4F7C-A1CC-7F263DD31188}.Release|x64.ActiveCfg = Release|x64 - {1611C59B-1C1E-4F7C-A1CC-7F263DD31188}.Release|x64.Build.0 = Release|x64 - {1611C59B-1C1E-4F7C-A1CC-7F263DD31188}.Release|x86.ActiveCfg = Release|x86 - {1611C59B-1C1E-4F7C-A1CC-7F263DD31188}.Release|x86.Build.0 = Release|x86 - {C7CB816D-0FB7-4C62-A225-98711E5537A5}.Debug|x64.ActiveCfg = Debug|x64 - {C7CB816D-0FB7-4C62-A225-98711E5537A5}.Debug|x64.Build.0 = Debug|x64 - {C7CB816D-0FB7-4C62-A225-98711E5537A5}.Debug|x86.ActiveCfg = Debug|x86 - {C7CB816D-0FB7-4C62-A225-98711E5537A5}.Debug|x86.Build.0 = Debug|x86 - {C7CB816D-0FB7-4C62-A225-98711E5537A5}.Release|x64.ActiveCfg = Release|x64 - {C7CB816D-0FB7-4C62-A225-98711E5537A5}.Release|x64.Build.0 = Release|x64 - {C7CB816D-0FB7-4C62-A225-98711E5537A5}.Release|x86.ActiveCfg = Release|x86 - {C7CB816D-0FB7-4C62-A225-98711E5537A5}.Release|x86.Build.0 = Release|x86 - {7B30B65B-220C-49AC-9448-C190F8F71715}.Debug|x64.ActiveCfg = Debug|x64 - {7B30B65B-220C-49AC-9448-C190F8F71715}.Debug|x64.Build.0 = Debug|x64 - {7B30B65B-220C-49AC-9448-C190F8F71715}.Debug|x86.ActiveCfg = Debug|x86 - {7B30B65B-220C-49AC-9448-C190F8F71715}.Debug|x86.Build.0 = Debug|x86 - {7B30B65B-220C-49AC-9448-C190F8F71715}.Release|x64.ActiveCfg = Release|x64 - {7B30B65B-220C-49AC-9448-C190F8F71715}.Release|x64.Build.0 = Release|x64 - {7B30B65B-220C-49AC-9448-C190F8F71715}.Release|x86.ActiveCfg = Release|x86 - {7B30B65B-220C-49AC-9448-C190F8F71715}.Release|x86.Build.0 = Release|x86 - {2557ED07-457A-415C-9C36-25C5ED163907}.Debug|x64.ActiveCfg = Debug|x64 - {2557ED07-457A-415C-9C36-25C5ED163907}.Debug|x64.Build.0 = Debug|x64 - {2557ED07-457A-415C-9C36-25C5ED163907}.Debug|x86.ActiveCfg = Debug|x86 - {2557ED07-457A-415C-9C36-25C5ED163907}.Debug|x86.Build.0 = Debug|x86 - {2557ED07-457A-415C-9C36-25C5ED163907}.Release|x64.ActiveCfg = Release|x64 - {2557ED07-457A-415C-9C36-25C5ED163907}.Release|x64.Build.0 = Release|x64 - {2557ED07-457A-415C-9C36-25C5ED163907}.Release|x86.ActiveCfg = Release|x86 - {2557ED07-457A-415C-9C36-25C5ED163907}.Release|x86.Build.0 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {72FFA8BA-53B0-4F29-A6D3-96DE7FF8EDFA} - EndGlobalSection -EndGlobal diff --git a/TextReplacementApp/App.config b/TextReplacementApp/App.config deleted file mode 100644 index 97ac932..0000000 --- a/TextReplacementApp/App.config +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TextReplacementApp/AppConfig.cs b/TextReplacementApp/AppConfig.cs index f789a77..3d859f8 100644 --- a/TextReplacementApp/AppConfig.cs +++ b/TextReplacementApp/AppConfig.cs @@ -1,41 +1,21 @@ -using Newtonsoft.Json; +namespace TextReplacementApp; -namespace TextReplacementApp +/// Represents the configuration settings of the application. +/// +/// This class encapsulates the configuration settings of the application, +/// including the directory path, search text, and replace text. +/// +public class AppConfig { - /// - /// Represents the configuration settings of the application. - /// - /// - /// This class encapsulates the configuration settings of the application, - /// including the directory path, search text, and replace text. - /// - public class AppConfig - { - /// - /// Gets or sets the directory path stored in the configuration. - /// - /// - /// A representing the directory path. - /// - [JsonProperty("directory_path")] - public string DirectoryPath { get; set; } + /// Gets or sets the directory path stored in the configuration. + /// A representing the directory path. + [JsonPropertyName("directoryPath")] public string DirectoryPath { get; set; } = string.Empty; - /// - /// Gets or sets the replace text stored in the configuration. - /// - /// - /// A representing the replace text. - /// - [JsonProperty("replace_with")] - public string ReplaceWith { get; set; } + /// Gets or sets the search text stored in the configuration. + /// A representing the search text. + [JsonPropertyName("findWhat")] public string FindWhat { get; set; } = string.Empty; - /// - /// Gets or sets the search text stored in the configuration. - /// - /// - /// A representing the search text. - /// - [JsonProperty("find_what")] - public string FindWhat { get; set; } - } -} \ No newline at end of file + /// Gets or sets the replace text stored in the configuration. + /// A representing the replace text. + [JsonPropertyName("replaceWith")] public string ReplaceWith { get; set; } = string.Empty; +} diff --git a/TextReplacementApp/FileFailureInfo.cs b/TextReplacementApp/FileFailureInfo.cs deleted file mode 100644 index c10e587..0000000 --- a/TextReplacementApp/FileFailureInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; - -namespace TextReplacementApp -{ - /// - /// Encapsulates information about a file that experienced an I/O operation - /// failure. - /// - public class FileFailureInfo - { - /// - /// Constructs a new instance of - /// and returns a reference to - /// it. - /// - public FileFailureInfo(string filePath, Exception exception) - { - FilePath = filePath; - Exception = exception; - } - - /// - /// Gets the that occurred. - /// - public Exception Exception { get; set; } - - /// - /// Gets a containing the fully-qualified pathname - /// of the file that experienced a failure. - /// - public string FilePath { get; } - } -} \ No newline at end of file diff --git a/TextReplacementApp/ListExtensions.cs b/TextReplacementApp/ListExtensions.cs deleted file mode 100644 index 2280937..0000000 --- a/TextReplacementApp/ListExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Linq; - -namespace TextReplacementApp -{ - /// - /// Exposes static extension methods for checking the contents of a list. - /// - public static class ListExtensions - { - /// - /// Compares the object with the - /// provided, to see if any of the - /// is a match. - /// - /// Type of the object to be tested. - /// Source object to check. - /// - /// Object or objects that should be compared to value - /// with the method. - /// - /// - /// True if any of the equals the value; - /// false otherwise. - /// - public static bool IsAnyOf(this T value, params T[] testObjects) - => testObjects.Contains(value); - } -} \ No newline at end of file diff --git a/TextReplacementApp/MainWindow.Designer.cs b/TextReplacementApp/MainWindow.Designer.cs index a16aa3c..9e5b794 100644 --- a/TextReplacementApp/MainWindow.Designer.cs +++ b/TextReplacementApp/MainWindow.Designer.cs @@ -1,22 +1,15 @@ -namespace TextReplacementApp +namespace TextReplacementApp { partial class MainWindow { - /// - /// Required designer variable. - /// + /// Required designer variable. private System.ComponentModel.IContainer components = null; - /// - /// Clean up any resources being used. - /// + /// Clean up any resources being used. /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { - if (disposing && (components != null)) - { - components.Dispose(); - } + if (disposing) components?.Dispose(); base.Dispose(disposing); } @@ -29,26 +22,52 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainWindow)); - this.findWhatTextBox = new System.Windows.Forms.TextBox(); + this.labelDirectoryPath = new System.Windows.Forms.Label(); + this.txtDirectoryPath = new System.Windows.Forms.TextBox(); + this.btnBrowse = new System.Windows.Forms.Button(); + this.folderBrowserDialog1 = new System.Windows.Forms.FolderBrowserDialog(); this.labelOriginal = new System.Windows.Forms.Label(); + this.txtSearchText = new System.Windows.Forms.TextBox(); this.labelReplacement = new System.Windows.Forms.Label(); - this.replaceWithTextBox = new System.Windows.Forms.TextBox(); + this.txtReplaceText = new System.Windows.Forms.TextBox(); this.btnDoIt = new System.Windows.Forms.Button(); - this.folderBrowserDialog1 = new System.Windows.Forms.FolderBrowserDialog(); - this.btnBrowse = new System.Windows.Forms.Button(); - this.txtDirectoryPath = new System.Windows.Forms.TextBox(); - this.labelDirectoryPath = new System.Windows.Forms.Label(); - this.switchButton = new System.Windows.Forms.Button(); this.SuspendLayout(); // - // txtSearchText + // labelDirectoryPath + // + this.labelDirectoryPath.AutoSize = true; + this.labelDirectoryPath.Location = new System.Drawing.Point(35, 23); + this.labelDirectoryPath.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.labelDirectoryPath.Name = "labelDirectoryPath"; + this.labelDirectoryPath.Size = new System.Drawing.Size(87, 15); + this.labelDirectoryPath.TabIndex = 0; + this.labelDirectoryPath.Text = "&Starting Folder:"; + // + // txtDirectoryPath + // + this.txtDirectoryPath.Location = new System.Drawing.Point(140, 20); + this.txtDirectoryPath.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.txtDirectoryPath.Name = "txtDirectoryPath"; + this.txtDirectoryPath.Size = new System.Drawing.Size(381, 23); + this.txtDirectoryPath.TabIndex = 1; + this.txtDirectoryPath.TextChanged += new System.EventHandler(this.OnTextChangedDirectoryPath); // - this.findWhatTextBox.Location = new System.Drawing.Point(141, 56); - this.findWhatTextBox.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.findWhatTextBox.Name = "findWhatTextBox"; - this.findWhatTextBox.Size = new System.Drawing.Size(380, 23); - this.findWhatTextBox.TabIndex = 4; - this.findWhatTextBox.TextChanged += new System.EventHandler(this.OnTextChangedSearchText); + // btnBrowse + // + this.btnBrowse.Location = new System.Drawing.Point(528, 17); + this.btnBrowse.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.btnBrowse.Name = "btnBrowse"; + this.btnBrowse.Size = new System.Drawing.Size(88, 27); + this.btnBrowse.TabIndex = 2; + this.btnBrowse.Text = "&Browse..."; + this.btnBrowse.UseVisualStyleBackColor = true; + this.btnBrowse.Click += new System.EventHandler(this.OnClickBrowseButton); + // + // folderBrowserDialog1 + // + this.folderBrowserDialog1.Description = "Select the folder containing the files to be processed"; + this.folderBrowserDialog1.RootFolder = System.Environment.SpecialFolder.MyComputer; + this.folderBrowserDialog1.ShowNewFolderButton = false; // // labelOriginal // @@ -57,9 +76,18 @@ private void InitializeComponent() this.labelOriginal.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); this.labelOriginal.Name = "labelOriginal"; this.labelOriginal.Size = new System.Drawing.Size(64, 15); - this.labelOriginal.TabIndex = 3; + this.labelOriginal.TabIndex = 0; this.labelOriginal.Text = "Find &What:"; // + // txtSearchText + // + this.txtSearchText.Location = new System.Drawing.Point(141, 53); + this.txtSearchText.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.txtSearchText.Name = "txtSearchText"; + this.txtSearchText.Size = new System.Drawing.Size(380, 23); + this.txtSearchText.TabIndex = 3; + this.txtSearchText.TextChanged += new System.EventHandler(this.OnTextChangedSearchText); + // // labelReplacement // this.labelReplacement.AutoSize = true; @@ -67,17 +95,17 @@ private void InitializeComponent() this.labelReplacement.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); this.labelReplacement.Name = "labelReplacement"; this.labelReplacement.Size = new System.Drawing.Size(79, 15); - this.labelReplacement.TabIndex = 6; + this.labelReplacement.TabIndex = 0; this.labelReplacement.Text = "&Replace With:"; // // txtReplaceText // - this.replaceWithTextBox.Location = new System.Drawing.Point(141, 92); - this.replaceWithTextBox.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.replaceWithTextBox.Name = "replaceWithTextBox"; - this.replaceWithTextBox.Size = new System.Drawing.Size(380, 23); - this.replaceWithTextBox.TabIndex = 7; - this.replaceWithTextBox.TextChanged += new System.EventHandler(this.OnTextChangedReplaceText); + this.txtReplaceText.Location = new System.Drawing.Point(141, 92); + this.txtReplaceText.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.txtReplaceText.Name = "txtReplaceText"; + this.txtReplaceText.Size = new System.Drawing.Size(380, 23); + this.txtReplaceText.TabIndex = 4; + this.txtReplaceText.TextChanged += new System.EventHandler(this.OnTextChangedReplaceText); // // btnDoIt // @@ -85,72 +113,25 @@ private void InitializeComponent() this.btnDoIt.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.btnDoIt.Name = "btnDoIt"; this.btnDoIt.Size = new System.Drawing.Size(88, 27); - this.btnDoIt.TabIndex = 8; + this.btnDoIt.TabIndex = 5; this.btnDoIt.Text = "&Do It!"; this.btnDoIt.UseVisualStyleBackColor = true; this.btnDoIt.Click += new System.EventHandler(this.OnClickDoItButton); // - // folderBrowserDialog1 - // - this.folderBrowserDialog1.Description = "Select the folder containing the files to be processed"; - this.folderBrowserDialog1.RootFolder = System.Environment.SpecialFolder.MyComputer; - this.folderBrowserDialog1.ShowNewFolderButton = false; - // - // btnBrowse - // - this.btnBrowse.Location = new System.Drawing.Point(528, 17); - this.btnBrowse.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.btnBrowse.Name = "btnBrowse"; - this.btnBrowse.Size = new System.Drawing.Size(88, 27); - this.btnBrowse.TabIndex = 2; - this.btnBrowse.Text = "&Browse..."; - this.btnBrowse.UseVisualStyleBackColor = true; - this.btnBrowse.Click += new System.EventHandler(this.OnClickBrowseButton); - // - // txtDirectoryPath - // - this.txtDirectoryPath.Location = new System.Drawing.Point(140, 20); - this.txtDirectoryPath.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.txtDirectoryPath.Name = "txtDirectoryPath"; - this.txtDirectoryPath.Size = new System.Drawing.Size(381, 23); - this.txtDirectoryPath.TabIndex = 1; - this.txtDirectoryPath.TextChanged += new System.EventHandler(this.OnTextChangedDirectoryPath); - // - // labelDirectoryPath - // - this.labelDirectoryPath.AutoSize = true; - this.labelDirectoryPath.Location = new System.Drawing.Point(35, 23); - this.labelDirectoryPath.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.labelDirectoryPath.Name = "labelDirectoryPath"; - this.labelDirectoryPath.Size = new System.Drawing.Size(87, 15); - this.labelDirectoryPath.TabIndex = 0; - this.labelDirectoryPath.Text = "&Starting Folder:"; - // - // switchButton - // - this.switchButton.Image = global::TextReplacementApp.Properties.Resources.SwitchUpDown_16x; - this.switchButton.Location = new System.Drawing.Point(538, 76); - this.switchButton.Name = "switchButton"; - this.switchButton.Size = new System.Drawing.Size(23, 23); - this.switchButton.TabIndex = 5; - this.switchButton.UseVisualStyleBackColor = true; - this.switchButton.Click += new System.EventHandler(this.OnClickSwitchButton); - // // MainWindow // this.AcceptButton = this.btnDoIt; this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(646, 180); - this.Controls.Add(this.switchButton); this.Controls.Add(this.labelDirectoryPath); this.Controls.Add(this.txtDirectoryPath); this.Controls.Add(this.btnBrowse); - this.Controls.Add(this.btnDoIt); - this.Controls.Add(this.replaceWithTextBox); - this.Controls.Add(this.labelReplacement); this.Controls.Add(this.labelOriginal); - this.Controls.Add(this.findWhatTextBox); + this.Controls.Add(this.txtSearchText); + this.Controls.Add(this.labelReplacement); + this.Controls.Add(this.txtReplaceText); + this.Controls.Add(this.btnDoIt); this.Font = new System.Drawing.Font("Segoe UI", 9F); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); @@ -166,15 +147,14 @@ private void InitializeComponent() #endregion - private System.Windows.Forms.TextBox findWhatTextBox; + private System.Windows.Forms.Label labelDirectoryPath; + private System.Windows.Forms.TextBox txtDirectoryPath; + private System.Windows.Forms.Button btnBrowse; + private System.Windows.Forms.FolderBrowserDialog folderBrowserDialog1; private System.Windows.Forms.Label labelOriginal; + private System.Windows.Forms.TextBox txtSearchText; private System.Windows.Forms.Label labelReplacement; - private System.Windows.Forms.TextBox replaceWithTextBox; + private System.Windows.Forms.TextBox txtReplaceText; private System.Windows.Forms.Button btnDoIt; - private System.Windows.Forms.FolderBrowserDialog folderBrowserDialog1; - private System.Windows.Forms.Button btnBrowse; - private System.Windows.Forms.TextBox txtDirectoryPath; - private System.Windows.Forms.Label labelDirectoryPath; - private System.Windows.Forms.Button switchButton; } } diff --git a/TextReplacementApp/MainWindow.cs b/TextReplacementApp/MainWindow.cs index 74bf084..32bfc5f 100644 --- a/TextReplacementApp/MainWindow.cs +++ b/TextReplacementApp/MainWindow.cs @@ -1,515 +1,194 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms; -using xyLOGIX.Files.MemoryMapped; -using Directory = Alphaleonis.Win32.Filesystem.Directory; -using File = Alphaleonis.Win32.Filesystem.File; -using Path = Alphaleonis.Win32.Filesystem.Path; - -namespace TextReplacementApp +namespace TextReplacementApp; + +/// Represents the main form of the TextReplacementApp application. +/// +/// The class provides the user interface for the application,
+/// allowing users to specify the directory where text replacement should occur and the text to search for and replace.
+/// It also initiates the text replacement process and displays progress using a progress dialog. +///
+public partial class MainWindow : Form { - /// - /// Represents the main form of the TextReplacementApp application. - /// + /// The application configuration settings. + /// This field stores the application configuration settings, including the directory path, search text, and replace text. + readonly AppConfig _appConfig; + + /// The fully-qualified pathname to the application configuration file. /// - /// The class provides the - /// user interface for the application, allowing users to specify the directory - /// where text replacement should occur and the text to search for and replace. It - /// also initiates the text replacement process and displays progress using a - /// progress dialog. + /// This field stores the location of the application configuration file on the file system.
+ /// The file is named config.json and is stored in the '%LOCALAPPDATA%\xyLOGIX\File Text Replacer Tool' directory. ///
- public partial class MainWindow : Form + readonly string _configFilePath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "xyLOGIX", + "File Text Replacer Tool", + "config.json"); + + /// Initializes a new instance of the class. + public MainWindow() { - /// - /// The application configuration settings. - /// - /// - /// This field stores the application configuration settings, - /// including the directory path, search text, and replace text. - /// - private readonly AppConfig appConfig; - - /// - /// The fully-qualified pathname to the application configuration file. - /// - /// - /// This field stores the location of the application configuration file - /// on the file system. The file is named config.json and is stored in the - /// %LOCALAPPDATA%\xyLOGIX\File Text Replacer Tool directory. - /// - private readonly string configFilePath = Path.Combine( - Environment.GetFolderPath( - Environment.SpecialFolder.LocalApplicationData - ), "xyLOGIX, LLC", "File Text Replacer Tool", "config.json" - ); - - /// - /// Constructs a new instance of and - /// returns a reference to it. - /// - public MainWindow() - { - InitializeComponent(); - - // Initialize AppConfig and load settings - appConfig = LoadConfig(); - UpdateTextBoxesFromConfig(); - } - - /// - /// Gets a reference to an instance of a collection, each of whose elements are of - /// type , that individually, - /// describe the file(s) for which I/O operation(s) failed and the reason(s) for - /// those failures. - /// - private IList FileFailures { get; } = - new List(); - - /// - /// Gets or sets the value of the Find What text box. - /// - public string FindWhat - { - [DebuggerStepThrough] get => findWhatTextBox.Text; - [DebuggerStepThrough] set => findWhatTextBox.Text = value; - } + InitializeComponent(); - /// - /// Gets or sets the value of the Replace With text box. - /// - public string ReplaceWith - { - [DebuggerStepThrough] get => replaceWithTextBox.Text; - [DebuggerStepThrough] set => replaceWithTextBox.Text = value; - } + _appConfig = LoadConfig(); // Initialize AppConfig and load settings. + UpdateTextBoxesFromConfig(); + } - /// - /// Raises the - /// event. - /// - /// - /// A - /// that contains the event data. - /// - protected override void OnFormClosing(FormClosingEventArgs e) - { - base.OnFormClosing(e); + /// Updates the text boxes on the main form with the values stored in the application configuration. + /// + /// This method retrieves the directory path, search text, and replace text from the object
+ /// and sets the corresponding text properties of the text boxes on the main form to these values. + ///
+ void UpdateTextBoxesFromConfig() + { + txtDirectoryPath.Text = _appConfig.DirectoryPath; + txtSearchText.Text = _appConfig.FindWhat; + txtReplaceText.Text = _appConfig.ReplaceWith; + } - // Save config when the form is closing - SaveConfig(); - } + /// Raises the event. + /// A that contains the event data. + protected override void OnFormClosing(FormClosingEventArgs e) + { + base.OnFormClosing(e); + SaveConfig(); // Save config when the form is closing. + } - /// - /// Loads the configuration settings from the specified JSON file. - /// - /// - /// An instance of - /// containing the loaded configuration settings. If the file does not exist, a new - /// instance of is returned. - /// - /// - /// This method reads the configuration settings from the specified JSON - /// file and deserializes them into an - /// - /// instance. If the file does not exist, a new instance of - /// is returned with all properties - /// set to empty strings. - /// - private AppConfig LoadConfig() - { - AppConfig result; + /// Event handler for the event of the Browse button. + /// The object that triggered the event. + /// The event arguments. + /// + /// This method is called when the user clicks the Browse button to select a directory using the folder browser dialog.
+ /// It updates the text of the directory path textbox with the selected directory path. + ///
+ void OnClickBrowseButton(object sender, EventArgs e) + { + var result = folderBrowserDialog1.ShowDialog(this); + if (result is DialogResult.OK && !string.IsNullOrWhiteSpace(folderBrowserDialog1.SelectedPath)) txtDirectoryPath.Text = folderBrowserDialog1.SelectedPath; + } - try - { - if (!File.Exists(configFilePath)) + /// + /// Event handler for the event of the Starting Folder text box.
+ /// Updates the property with the trimmed text from the directory path text box. + ///
+ /// The object that raised the event. + /// The event data. + void OnTextChangedDirectoryPath(object sender, EventArgs e) => _appConfig.DirectoryPath = txtDirectoryPath.Text.Trim(); - // If config file doesn't exist, return a new instance - return new AppConfig(); + /// + /// Event handler for the event of the Find What text box.
+ /// Updates the property with the trimmed text from the directory path text box. + ///
+ /// The object that raised the event. + /// The event data. + void OnTextChangedSearchText(object sender, EventArgs e) => _appConfig.FindWhat = txtSearchText.Text.Trim(); - // Load config from JSON file - var json = File.ReadAllText(configFilePath); - result = JsonConvert.DeserializeObject(json); - } - catch (Exception ex) - { - // display an alert with the exception text - MessageBox.Show( - this, ex.Message, Application.ProductName, - MessageBoxButtons.OK, MessageBoxIcon.Stop - ); + /// + /// Event handler for the event of the Replace With text box.
+ /// Updates the property with the trimmed text from the directory path text box. + ///
+ /// The object that raised the event. + /// The event data. + void OnTextChangedReplaceText(object sender, EventArgs e) => _appConfig.ReplaceWith = txtReplaceText.Text.Trim(); - result = default; - } + /// Event handler for the event of the Do It button. + /// The object that triggered the event. + /// The event arguments. + /// + /// This method is called when the user clicks the Do It button to initiate the text replacement process.
+ /// It performs validation, creates a progress dialog, starts a background task for text replacement, and displays the progress dialog modally. + ///
+ void OnClickDoItButton(object sender, EventArgs e) + { + var directoryPath = txtDirectoryPath.Text.Trim(); + var searchText = txtSearchText.Text.Trim(); + var replaceText = txtReplaceText.Text.Trim(); - return result; - } + // Validation. + var text = string.Empty; + if (string.IsNullOrEmpty(directoryPath) || !Directory.Exists(directoryPath)) text = "Please select a valid directory."; + else if (string.IsNullOrEmpty(searchText)) text = "Please type in some text to find."; + else if (string.IsNullOrEmpty(replaceText)) text = "Please type in some text to replace the found text with."; - /// - /// Event handler for the event - /// event of the Browse button. - /// - /// The object that triggered the event. - /// The event arguments. - /// - /// This method is called when the user clicks the Browse button to - /// select a directory using the folder browser dialog. It updates the text of the - /// directory path textbox with the selected directory path. - /// - private void OnClickBrowseButton(object sender, EventArgs e) + if (!string.IsNullOrEmpty(text)) { - var result = folderBrowserDialog1.ShowDialog(this); - if (result == DialogResult.OK && - !string.IsNullOrWhiteSpace(folderBrowserDialog1.SelectedPath)) - txtDirectoryPath.Text = folderBrowserDialog1.SelectedPath; + _ = MessageBox.Show(text, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error); + return; } - /// - /// Event handler for the event - /// of the Do It button. - /// - /// The object that triggered the event. - /// The event arguments. - /// - /// This method is called when the user clicks the Do It button - /// to initiate the text replacement process. It performs validation, - /// creates a progress dialog, starts a background task for text replacement, - /// and displays the progress dialog modally. - /// - private void OnClickDoItButton(object sender, EventArgs e) - { - var directoryPath = txtDirectoryPath.Text.Trim(); - var searchText = findWhatTextBox.Text.Trim(); - var replaceText = replaceWithTextBox.Text.Trim(); - - // validation - if (string.IsNullOrEmpty(directoryPath) || - !Directory.Exists(directoryPath)) - { - MessageBox.Show( - "Please select a valid directory.", Application.ProductName, - MessageBoxButtons.OK, MessageBoxIcon.Error - ); - return; - } - - if (string.IsNullOrEmpty(searchText)) - { - MessageBox.Show( - "Please type in some text to find.", - Application.ProductName, MessageBoxButtons.OK, - MessageBoxIcon.Error - ); - return; - } + using var progressDialog = new ProgressDialog(); - if (string.IsNullOrEmpty(replaceText)) - { - MessageBox.Show( - "Please type in some text to replace the found text with.", - Application.ProductName, MessageBoxButtons.OK, - MessageBoxIcon.Error - ); - return; - } + // Use Progress to report progress. + var progressReporter = new Progress(report + => progressDialog.UpdateProgress(report.CurrentFile, report.ProgressPercentage)); - using (var progressDialog = new ProgressDialog()) + // Start a new task for text replacement. + _ = Task.Run(() => { - // Use Progress to report progress - var progressReporter = new Progress( - report => - { - progressDialog.UpdateProgress( - report.CurrentFile, report.ProgressPercentage - ); - } - ); - - // Start a new task for text replacement - Task.Run( - () => - { - ReplaceTextInFiles( - directoryPath, searchText, replaceText, - progressReporter - ); + var res = Common.ReplaceTextInFiles(directoryPath, searchText, replaceText, progressReporter, this); - // close the progress dialog - if (InvokeRequired) - progressDialog.BeginInvoke( - new MethodInvoker(progressDialog.Close) - ); - else - progressDialog.Close(); + // Close the progress dialog. + if (InvokeRequired) + _ = progressDialog.BeginInvoke(new MethodInvoker(progressDialog.Close)); + else + progressDialog.Close(); - // Show completion message - unless there are more than zero failures - if (FileFailures.Count <= 0) - MessageBox.Show( - "Text replacement completed.", "Success", - MessageBoxButtons.OK, MessageBoxIcon.Information - ); - } - ); + var text = $""" + Text replacement completed. - progressDialog.ShowDialog(this); - } - } - - /// - /// Handles the event raised by - /// the Switch button when it is clicked by the user. - /// - /// - /// Reference to an instance of the object that raised the - /// event. - /// - /// - /// A that contains the event - /// data. - /// - /// - /// This method responds by juxtaposing the values of the Find What - /// and Replace With text boxes. - /// - private void OnClickSwitchButton(object sender, EventArgs e) - { - if (string.IsNullOrWhiteSpace(FindWhat) && - string.IsNullOrWhiteSpace(ReplaceWith)) - return; // nothing to do - - (ReplaceWith, FindWhat) = (FindWhat, ReplaceWith); - } + Elapsed time {res.ElapsedMilliseconds:n0} ms. - /// - /// Event handler for the - /// event of the - /// Starting Folder text box. Updates the - /// property with the - /// trimmed text from the directory path text box. - /// - /// The object that raised the event. - /// The event data. - private void OnTextChangedDirectoryPath(object sender, EventArgs e) - => appConfig.DirectoryPath = txtDirectoryPath.Text.Trim(); + Files Total .......: {res.TotalFiles:n0} + Completed .: {res.CompletedFiles:n0} + Replaced ....: {res.ReplacedFiles:n0} + """; - /// - /// Event handler for the - /// event of the - /// Replace With text box. Updates the - /// property with the - /// trimmed text from the directory path text box. - /// - /// The object that raised the event. - /// The event data. - private void OnTextChangedReplaceText(object sender, EventArgs e) - => appConfig.ReplaceWith = replaceWithTextBox.Text.Trim(); + // Show completion message ('text...' the formatting makes one cry). + _ = MessageBox.Show(text, "Success", MessageBoxButtons.OK, MessageBoxIcon.Information); + }); - /// - /// Event handler for the - /// event of the - /// Find What text box. Updates the - /// property with the - /// trimmed text from the directory path text box. - /// - /// The object that raised the event. - /// The event data. - private void OnTextChangedSearchText(object sender, EventArgs e) - => appConfig.FindWhat = findWhatTextBox.Text.Trim(); + _ = progressDialog.ShowDialog(this); + } - /// - /// Replaces text in the specified file. - /// - /// The path of the file to perform text replacement on. - /// The text to search for in the file. - /// The text to replace the search text with. - /// - /// This method performs text replacement in the specified file. It reads - /// the content of the file, replaces occurrences of the search text with the - /// replace text, and writes the modified content back to the file. If the file - /// path contains specific directories (such as .git, .vs, etc.), it - /// skips the replacement. - /// - private void ReplaceTextInFile( - string filePath, - string searchText, - string replaceText - ) + /// Loads the configuration settings from the specified JSON file. + /// + /// This method reads the configuration settings from the specified JSON file and deserializes them into an instance.
+ /// If the file does not exist, a new instance of is returned with all properties set to empty strings. + ///
+ /// + /// An instance of containing the loaded configuration settings. + /// If the file does not exist, a new instance of is returned. + /// + AppConfig LoadConfig() + { + try // If config file doesn't exist, return a new instance. { - try + if (File.Exists(_configFilePath)) { - /* - * Account for this algorithm being run on a - * Visual Studio solution consisting only of - * C# projects, and in a local Git repo. - */ - - if (string.IsNullOrWhiteSpace(filePath)) return; - - if (filePath.Contains(@"\.git\")) return; - if (filePath.Contains(@"\.vs\")) return; - if (filePath.Contains(@"\packages\")) return; - if (filePath.Contains(@"\bin\")) return; - if (filePath.Contains(@"\obj\")) return; - if (!Path.GetExtension(filePath) - .IsAnyOf( - ".txt", ".cs", ".resx", ".config", ".json", - ".csproj", ".settings", ".md" - )) - return; - - if (!File.Exists(filePath)) return; - - var text = string.Empty; - var originalLength = 0L; - - using (var reader = new FileStreamReader(filePath)) - { - originalLength = reader.Length; - - text = reader.ReadAllText(); - } - - if (!text.Contains(searchText)) return; - - // Perform text replacement - var newText = text.Replace(searchText, replaceText).Trim(); - - // Calculate the length of the modified text - long modifiedLength = Encoding.UTF8.GetByteCount(newText); - - // No sense in writing out the replacement text if no replacement actually took place - if (newText.Equals(text) && - modifiedLength.Equals(originalLength)) return; - - using (var writer = new FileStreamWriter(filePath)) - { - writer.SetLength(modifiedLength); - - writer.WriteAllText(newText); - } - } - catch (Exception ex) - { - if (typeof(UnauthorizedAccessException) != ex.GetType()) - FileFailures.Add(new FileFailureInfo(filePath, ex)); + var json = File.ReadAllText(_configFilePath); // Load config from JSON file. + return JsonSerializer.Deserialize(json) ?? new(); } } + catch (Exception ex) { Common.DisplayException(ex); } // Display an alert with the exception text. - /// - /// Replaces text in all files within the specified directory and its - /// subdirectories. - /// - /// The path of the directory to search for files. - /// The text to search for in each file. - /// The text to replace the search text with. - /// - /// An object for reporting progress during text - /// replacement. - /// - /// - /// This method recursively searches for files within the specified - /// directory and its subdirectories. For each file found, it calls - /// to perform text replacement. Progress is - /// reported using the specified . Certain - /// directories (e.g., .git, .vs, etc.) are excluded from text - /// replacement. - /// - private void ReplaceTextInFiles( - string directoryPath, - string searchText, - string replaceText, - IProgress progressReporter - ) - { - var files = Directory.EnumerateFiles( - directoryPath, "*", - SearchOption.AllDirectories - ) - .Where( - file => !file.Contains(@"\.git\") && - !file.Contains(@"\.vs\") && - !file.Contains(@"\packages\") && - !file.Contains(@"\bin\") && - !file.Contains(@"\obj\") - ) - .ToList(); - - var totalFiles = files.Count; - var completedFiles = 0; - - foreach (var file in files) - { - ReplaceTextInFile(file, searchText, replaceText); - Interlocked.Increment(ref completedFiles); - - // Report progress - var progressPercentage = - (int)((double)completedFiles / totalFiles * 100); - var progressReport = new ProgressReport( - file, progressPercentage - ); - progressReporter.Report(progressReport); - } - } + return new(); + } - /// - /// Saves the current application configuration to a JSON file. - /// - /// - /// The method first checks if the directory containing the configuration - /// file exists, and creates it if it does not. Then, it serializes the - /// object to JSON format using - /// Newtonsoft.Json, and writes the JSON string to the configuration file. - /// - private void SaveConfig() + /// Saves the current application configuration to a JSON file. + /// + /// The method first checks if the directory containing the configuration file exists, and creates it if it does not.
+ /// Then, it serializes the object to JSON format using System.Text.Json, and writes the JSON string to the configuration file. + ///
+ void SaveConfig() + { + try // Check for any conditions that might prevent us from succeeding. { - try - { - // check for any conditions that might prevent us from succeeding. - if (string.IsNullOrWhiteSpace(configFilePath)) return; - - var directory = Path.GetDirectoryName(configFilePath); - if (!Directory.Exists(directory)) - Directory.CreateDirectory(directory); + var json = JsonSerializer.Serialize(_appConfig, new JsonSerializerOptions { WriteIndented = true }); - var json = JsonConvert.SerializeObject( - appConfig, Formatting.Indented - ); - if (string.IsNullOrWhiteSpace(json)) return; - - File.WriteAllText(configFilePath, json); - } - catch (Exception ex) + if (!string.IsNullOrWhiteSpace(json) && Path.GetDirectoryName(_configFilePath) is string directory) { - // display an alert with the exception text - MessageBox.Show( - this, ex.Message, Application.ProductName, - MessageBoxButtons.OK, MessageBoxIcon.Stop - ); + _ = Directory.CreateDirectory(directory); + File.WriteAllText(_configFilePath, json); } } - - /// - /// Updates the text boxes on the main form with the values stored in the - /// application configuration. - /// - /// - /// This method retrieves the directory path, search text, and replace - /// text from the object and sets the - /// corresponding text properties of the text boxes on the main form to these - /// values. - /// - private void UpdateTextBoxesFromConfig() - { - txtDirectoryPath.Text = appConfig.DirectoryPath; - findWhatTextBox.Text = appConfig.FindWhat; - replaceWithTextBox.Text = appConfig.ReplaceWith; - } + catch (Exception ex) { Common.DisplayException(ex); } // Display an alert with the exception text. } -} \ No newline at end of file +} diff --git a/TextReplacementApp/MainWindow.resx b/TextReplacementApp/MainWindow.resx index d82ac7d..e0b42a6 100644 --- a/TextReplacementApp/MainWindow.resx +++ b/TextReplacementApp/MainWindow.resx @@ -1,17 +1,17 @@ - + - @@ -123,258 +123,24 @@ - AAABAAMAEBAAAAEAIABoBAAANgAAACAgAAABACAAqBAAAJ4EAAAwMAAAAQAgAKglAABGFQAAKAAAABAA - AAAgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKenp8bW1tb+Pj4+Sn9/ - f8ihoaH+Li4uSGVlZcp/f3/+IiIiWQAAAPAAAADyGhoaTn9/f/1sbGzYAgICBAAAAAAxMTE61tbW/rW1 - tdYrKytCoaGh/oeHh9UiIiJDf39//mpqatMCAgJ7AAAA/y4uLvt/f3//KCgoUAAAAAAAAAAAAAAAAIuL - i6XW1tb/Xl5ecGpqaqehoaH/RkZGblVVVamAgID/NjY2cwAAANwAAAD/Q0NDuwAAAAAAAAAAAAAAAAAA - AAAYGBgczs7O9MnJye4kJCQym5ub9ZaWlu0cHBwye3t79nZ2duxJSUnjAAAA/wAAAMQAAAAAAAAAAAAA - AAAAAAAAAAAAAGpqan7W1tb/f39/l1FRUYChoaH/Xl5elUFBQYJ/f3//f39//x0dHfcAAAD/AAAAWQAA - AAAAAAAAAAAAAAAAAAAICAgJxMTE6dTU1PsnJycxlZWV6p+fn/soKChHf39//4CAgP8aGhplAAAA/wAA - AOEAAAADAAAAAAAAAAAAAAAAKioqMtXV1fy6urrdJycnPKCgoP2Li4vcISEhP39/f/+AgID/ICAgrwAA - AP8AAACoAAAAAAAAAAAAAAAAAAAAAKSkpMLW1tb/RkZGU3x8fMShoaH/MzMzUWNjY8Z/f3//dnZ2/ggI - CP8AAAD1AAAAHgAAAAAAAAAAAAAAAElJSVfW1tb+oKCgvjk5OVqhoaH+d3d3vC4uLlyAgID+XV1duhcX - F8QAAAD+DQ0NnAAAAAAAAAAAAAAAAAgICAm9vb3g1NTU+y4uLjmPj4/hn5+f+yIiIjhxcXHifn5++xUV - FVgAAAD7Dg4O/3FxcewJCQkTAAAAAAAAAABpaWl91tbW/39/f5dQUFB/oaGh/19fX5VAQECBf39//kpK - SpMAAAC6AAAA/zY2NsZ/f3//SkpKkwAAAAAAAAAAZ2dnenJycogSEhIVTk5Oe1ZWVocNDQ0VPj4+fERE - RIcJCQkmAAAAiAAAAHsFBQUMQ0NDhkFBQYIBAQECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAACAAAABAAAAAAQAgAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEB - AQKvr6/Q1tbW/tbW1v7W1tb+Pj4+SgICAgOGhobToaGh/qGhof6hoaH+LCwsRgICAgRra2vWf39//n9/ - f/5/f3/9ISEhQgAAAE4AAAD+AAAA/gAAAP4AAADLAAAAAhISEiR8fHz3f39//oCAgP52dnbtCQkJEgAA - AAAAAAAAAAAAAEBAQEvW1tb/1tbW/tbW1v+1tbXWBAQEBTIyMlChoaH/oaGh/qGhof+GhobTAgICBCoq - KlR/f3//f39//oCAgP5oaGjQAQEBBAAAAMYAAAD+AAAA/wAAAP4AAABhWFhYsH9/f/9/f3/+f39//zw8 - PHgAAAAAAAAAAAAAAAAAAAAAAAAAAZ6enrzW1tb+1tbW/tfX1/5dXV1uAAAAAXl5eb+hoaH+oaGh/qGh - of5DQ0NqAAAAAWJiYsN/f3/+f39//n9/f/4zMzNmAAAAOgAAAP0AAAD+AAAA/gQEBO95eXn+f39//n9/ - f/5wcHDfBAQECQAAAAAAAAAAAAAAAAAAAAAAAAAAJycnLtPT0/vW1tb/1tbW/snJye4REREUHx8fMaCg - oPuhoaH/oaGh/paWluwLCwsSGhoaNX5+fvx/f3/+gICA/3V1deoICAgQAAAApQAAAP8AAAD+AAAA/zs7 - O/5/f3//f39//isrK1YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfX19lNbW1v7W1tb+1tbW/n9/ - f5cAAAAAYGBgmKGhof6hoaH+oaGh/l1dXZMAAAAATk5OnH9/f/6AgID+f39//kdHR48AAAAbAAAA8wAA - AP4AAAD+AwMD/m5ubv5gYGDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAUyMjI7tbW - 1v7W1tb/09PT+igoKC8ODg4WmJiY8KGhof6hoaH/np6e+RwcHCwMDAwYeXl58n9/f/+AgID+fHx8+BQU - FCkAAAB+AAAA/gAAAP8AAAD+Jycn/RgYGDEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AABdXV1v19fX/tbW1v7W1tb+n5+fvQAAAABJSUlzoaGh/qGhof6hoaH+dXV1uQAAAAA8PDx3gICA/n9/ - f/5/f3/+W1tbtURERJIODg7+AAAA/gAAAP4AAAD+AAAAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAMDAwSzs7PV1tbW/9bW1v7W1tb/REREUQMDAwWJiYnYoaGh/6Ghof6hoaH/MDAwTQMD - AwZubm7bgICA/n9/f/9/f3/+fn5+/FRUVP4AAAD/AAAA/gAAAP8AAADSAAAAAwAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAD09PUnW1tb+1tbW/tbW1v66urrcBgYGCDAwME2hoaH+oaGh/qGh - of6KioraBAQEBigoKFGAgID+f39//n9/f/5/f3/+f39//h4eHv4AAAD+AAAA/gAAAP4AAABqAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJeXl7PW1tb+1tbW/9bW1v5lZWV4AAAAAHR0 - dLehoaH+oaGh/6Ghof5KSkp0AAAAAF5eXrt/f3//f39//oCAgP9/f3/+WVlZ5AAAAPwAAAD/AAAA/gAA - AOsAAAARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAgJtLS0vnW1tb+1tbW/svL - y/EWFhYaGhoaKZ6envqhoaH+oaGh/piYmO8PDw8YGRkZM39/f/5/f3/+gICA/n9/f/4rKytXAAAAmwAA - AP4AAAD+AAAA/gAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkpKSrtbW - 1v/W1tb+19fX/2xsbIAAAAAAcXFxsqGhof+hoaH+oaGh/k9PT3wqKipUf39//4CAgP5/f3//f39//jw8 - PHgAAAAqAAAA/wAAAP4AAAD/AAAA+AAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABoa - Gh/Pz8/21tbW/tbW1v7Ozs70GxsbIRYWFiKdnZ33oaGh/qGhof6amprzExMTHhgYGDB/f3/+gICA/n9/ - f/5/f3/+KioqVAAAAJIAAAD+AAAA/gAAAP4AAACaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAjo6OqNbW1v7W1tb/1tbW/m5uboMAAAAAbW1traGhof6hoaH/oaGh/lBQUH8AAAAAWFhYsYCA - gP9/f3/+gICA/39/f/5ZWVncAAAA+gAAAP8AAAD+AAAA8AAAABYAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAADU1NT/W1tb+1tbW/tbW1v7AwMDkCQkJCyoqKkKhoaH+oaGh/qGhof6Pj4/hBgYGCSMj - I0Z/f3/+f39//n9/f/6AgID+f39//iIiIv4AAAD+AAAA/gAAAP4AAAB0AAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAABAQEBra2tztbW1v/W1tb+1tbW/0xMTFoBAQEChISE0aGhof+hoaH+oaGh/zY2 - NlYBAQEDampq1H9/f/5/f3//f39//n9/f/5ZWVn+AAAA/wAAAP4AAAD/AAAA2QAAAAQAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAFVVVWXW1tb+1tbW/tbW1v6np6fGAQEBAUNDQ2mhoaH+oaGh/qGh - of57e3vDAAAAATc3N21/f3/+gICA/oCAgP5gYGC/TExMnRISEv4AAAD+AAAA/gAAAP4AAABNAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANDQ0PxMTE6dbW1v7W1tb+1NTU/C4uLjcKCgoRlZWV66Gh - of6hoaH+oKCg/CEhITQJCQkSd3d37oCAgP5/f3/+fn5++xgYGDAAAAB2AAAA/gAAAP4AAAD+ISEh/BQU - FCkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHR0dIrW1tb/1tbW/tbW1v+IiIihAAAAAFpa - Wo6hoaH/oaGh/qGhof9jY2OdAAAAAElJSZKAgID+f39//4CAgP5MTEyZAAAAFQAAAO8AAAD+AAAA/wEB - Af5qamr/W1tbtgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAm0dHR+NbW1v7W1tb+zMzM8hYW - FhoaGhopnp6e+aGhof6hoaH+mZmZ8A8PDxgWFhYsfX19+n9/f/6AgID+eHh47woKChUAAACbAAAA/gAA - AP4AAAD+NjY2/n9/f/5/f3/+JiYmTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJaWlrLW1tb+1tbW/9fX - 1/5lZWV4AAAAAHR0dLahoaH+oaGh/6Ghof5JSUl0AAAAAF1dXbqAgID+f39//oCAgP84ODhwAAAAMAAA - APwAAAD/AAAA/gQEBPR3d3f+f39//39/f/5sbGzYAwMDBgAAAAAAAAAAAAAAAAAAAAA5OTlD1tbW/tbW - 1v7W1tb+u7u73QcHBwktLS1HoaGh/qGhof6hoaH+i4uL2gQEBAcmJiZLf39//n9/f/5/f3/+bGxs2AMD - AwYAAAC+AAAA/gAAAP4AAAD+AAAAbF1dXbp/f3/+f39//oCAgP44ODhvAAAAAAAAAAAAAAAAAAAAAKmp - qcjW1tb/1tbW/tbW1v5HR0dUAAAAAYGBgcyhoaH/oaGh/qGhof4zMzNQAQEBAmhoaM9/f3//f39//n9/ - f/4mJiZMAAAARQAAAP4AAAD+AAAA/wAAANQAAAAEFhYWLH5+fvt/f3/+f39//3R0dOgEBAQIAAAAAAAA - AAABAQEBDg4OEQ4ODhEODg4RDg4OEAAAAAABAQEBCgoKEQsLCxEKCgoRCgoKEAAAAAABAQECCAgIEQgI - CBEICAgRCAgIEAAAAAAAAAAJAAAAEQAAABEAAAARAAAACAAAAAAAAAAABgYGDQgICBEICAgRCAgIEQAA - AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAwAAAAYAAAAAEA - IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - 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 - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + AAABAAIAICAQAAEABADoAgAAJgAAABAQEAABAAQAKAEAAA4DAAAoAAAAIAAAAEAAAAABAAQAAAAAAAAC + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAMDAwACAgIAAAAD/AAD/ + AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAH//////93iAAAAAAAAACP/3d3 + d3d3d3eIAAAAAAAAj///d3d3d3d3iAAAAAAAAAiHd/d3d3d3eIAAAAAAAAAACIiPd3d3iIgAAAAAAAAA + AAAAj3d3eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIiIiIiIiIiIiIiIiIAAAAj3d3d3d3d3d3d3d3eI + AAAI9//////////////3iIAACPcAAAAAAAAAAAAA94iAAAj3AAAAAAAAAAAAAPeIgAAI9wAAAAAAAAAA + AAD3iIAACPcAAAAAAAAAAAAA94iAAAj3AAAAAJmQmZAAmfeIgAAI9wAAAAkAkJAJCQD3iIAACPcAAAAA + mZCQCQkA94iAAAj3AAAACQCQmQkJAPeIgAAI9wAAAACZAJCQAJn3iIAACPcAAAAAAACQAAAA94iAAAj3 + AAAAAAAAkAAAAPeIgAAI9wAAAAAAAAAAAAD3iIAACPcAAAAAAAAAAAAA94iAAAj3AAAAAAAAAAAAAPeI + gAAI9wAAAAAAAAAAAAD3iIAACPeIiIiIiIiIiIiI94iAAAj3d3d3d3d3d3d3d3eIgAAAj/////////// + ////iIAAAAh3d3d3d3d3d3d3d/iAAAAAh3d3d3d3d3d3d3d/gAAAAAiIiIiIiIiIiIiIiIAA/gAD//gA + AP/wAAB/8AAAf/gAAP/+AAP//8Af/8AAAB+AAAAPgAAAB4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AA + AAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA4AAAAOAAAADgAAAA8AAAAPgAAAD8AAAA/gA + AAcoAAAAEAAAACAAAAABAAQAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAgAAAAICAAIAA + AACAAIAAgIAAAMDAwACAgIAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAd3d3d4 + AAAACI93eIgAAAAACPeAAAAAAAAAAAAAAACIiIiIiIiAAI//////94gAjwAAAAAHiACPAAAAAAeIAI8A + AJkJB4gAjwAAkJCXiACPAAAAAAeIAI8AAAAAB4gAh///////iAAId3d3d3f4AACIiIiIiIgA4A8AAMAH + AADgDwAA+D8AAIAHAAAAAwAAAAEAAAABAAAAAQAAAAEAAAABAAAAAf8AAAGgAAABgACAAf8AwAMAAA== \ No newline at end of file diff --git a/TextReplacementApp/Program.cs b/TextReplacementApp/Program.cs index 9d801ae..f182995 100644 --- a/TextReplacementApp/Program.cs +++ b/TextReplacementApp/Program.cs @@ -1,22 +1,14 @@ -using System; -using System.Windows.Forms; +namespace TextReplacementApp; -namespace TextReplacementApp +/// Defines the behavior of the application. +static class Program { - /// - /// Defines the behavior of the application. - /// - public static class Program + /// The main entry point for the application. + [STAThread] + static void Main() // To customize application configuration such as set high DPI settings or default font, see https://aka.ms/applicationconfiguration. { - /// - /// The main entry point for the application. - /// - [STAThread] - public static void Main() - { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new MainWindow()); - } + ApplicationConfiguration.Initialize(); + using var mainForm = new MainWindow(); // After 'ApplicationConfiguration.Initialize()'. + Application.Run(mainForm); } -} \ No newline at end of file +} diff --git a/TextReplacementApp/ProgressDialog.cs b/TextReplacementApp/ProgressDialog.cs deleted file mode 100644 index c9594d3..0000000 --- a/TextReplacementApp/ProgressDialog.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Windows.Forms; - -namespace TextReplacementApp -{ - /// - /// Represents a dialog used to display the progress of an operation. - /// - /// - /// The class provides - /// a simple dialog for displaying progress information during long-running - /// operations. It contains a label to show the current file being processed and a - /// progress bar to indicate the overall progress of the operation. - /// - public partial class ProgressDialog : Form - { - /// - /// Constructs a new instance of - /// and returns a reference to it. - /// - public ProgressDialog() - => InitializeComponent(); - - /// - /// Updates the progress of the operation being displayed in the progress dialog. - /// - /// The path of the current file being processed. - /// The percentage of completion of the operation. - /// - /// This method updates the text displayed for the current file being processed - /// and adjusts the progress bar to reflect the progress percentage. - /// If invoked from a different thread than the one that created the control, - /// it will use to marshal the call to the proper - /// thread. - /// - public void UpdateProgress(string filePath, int progressPercentage) - { - if (InvokeRequired) - { - Invoke( - new Action( - () => UpdateProgress(filePath, progressPercentage) - ) - ); - return; - } - - lblFilePath.Text = filePath; - progressBar.Value = progressPercentage; - } - - /// Raises the event. - /// - /// An that contains the event - /// data. - /// - protected override void OnLoad(EventArgs e) - { - base.OnLoad(e); - - Text = Application.ProductName; - } - } -} \ No newline at end of file diff --git a/TextReplacementApp/ProgressReport.cs b/TextReplacementApp/ProgressReport.cs deleted file mode 100644 index c5c1392..0000000 --- a/TextReplacementApp/ProgressReport.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace TextReplacementApp -{ - /// - /// Represents a progress report containing information about the current file - /// being processed - /// and the progress percentage. - /// - public class ProgressReport - { - /// - /// Initializes a new instance of the - /// class with the specified - /// current file and progress percentage. - /// - /// The path of the current file being processed. - /// The progress percentage of the operation. - public ProgressReport(string currentFile, int progressPercentage) - { - CurrentFile = currentFile; - ProgressPercentage = progressPercentage; - } - - /// - /// Gets the path of the current file being processed. - /// - public string CurrentFile { get; } - - /// - /// Gets the progress percentage of the operation. - /// - public int ProgressPercentage { get; } - } -} \ No newline at end of file diff --git a/TextReplacementApp/Properties/AssemblyInfo.cs b/TextReplacementApp/Properties/AssemblyInfo.cs deleted file mode 100644 index ff651b2..0000000 --- a/TextReplacementApp/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TextReplacementApp")] -[assembly: AssemblyDescription("Small Windows Forms utility that illustrates quickly replacing text in all files wihtin a certain directory tree, even if there are millions of them, very rapidly. This project is customized and adapted specifically for those directory trees that contain C# Solution(s) written using Visual Studio 2022 or higher. The code in this application is made freely available to copy, distribute, and/or reproduce in any medium, at any time, in any manner or format.")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("xyLOGIX, LLC")] -[assembly: AssemblyProduct("xyLOGIX Fast Text Replacer Tool")] -[assembly: AssemblyCopyright("Copyright © 2024 by xyLOGIX, LLC. All rights given up freely.")] -[assembly: AssemblyTrademark("This software component is a part of the xyLOGIX Fast Text Replacer Tool software system. Fast Text Replacer Tool is a trademark of xyLOGIX, LLC. All rights reserved, especially those granted by international treaties. All other trademarks appearing in this software system's code or its user interface belong to their respective owners.")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("a4f06817-13b0-4937-ade2-c34f8038bcd1")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TextReplacementApp/Properties/Resources.Designer.cs b/TextReplacementApp/Properties/Resources.Designer.cs deleted file mode 100644 index df9556b..0000000 --- a/TextReplacementApp/Properties/Resources.Designer.cs +++ /dev/null @@ -1,73 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace TextReplacementApp.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TextReplacementApp.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - internal static System.Drawing.Bitmap SwitchUpDown_16x { - get { - object obj = ResourceManager.GetObject("SwitchUpDown_16x", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - } -} diff --git a/TextReplacementApp/Properties/Resources.resx b/TextReplacementApp/Properties/Resources.resx deleted file mode 100644 index 5345810..0000000 --- a/TextReplacementApp/Properties/Resources.resx +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - ..\Resources\SwitchUpDown_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - \ No newline at end of file diff --git a/TextReplacementApp/Properties/Settings.Designer.cs b/TextReplacementApp/Properties/Settings.Designer.cs deleted file mode 100644 index c417875..0000000 --- a/TextReplacementApp/Properties/Settings.Designer.cs +++ /dev/null @@ -1,30 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace TextReplacementApp.Properties -{ - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default - { - get - { - return defaultInstance; - } - } - } -} diff --git a/TextReplacementApp/Properties/Settings.settings b/TextReplacementApp/Properties/Settings.settings deleted file mode 100644 index 3964565..0000000 --- a/TextReplacementApp/Properties/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/TextReplacementApp/3D Text.ico b/TextReplacementApp/Resources/3D Text.ico similarity index 100% rename from TextReplacementApp/3D Text.ico rename to TextReplacementApp/Resources/3D Text.ico diff --git a/TextReplacementApp/newxylogix.ICO b/TextReplacementApp/Resources/newxylogix.ico similarity index 100% rename from TextReplacementApp/newxylogix.ICO rename to TextReplacementApp/Resources/newxylogix.ico diff --git a/TextReplacementApp/TextReplacementApp.csproj b/TextReplacementApp/TextReplacementApp.csproj index 0fd33c7..3722213 100644 --- a/TextReplacementApp/TextReplacementApp.csproj +++ b/TextReplacementApp/TextReplacementApp.csproj @@ -1,197 +1,30 @@ - - - + + - Debug - AnyCPU - {A4F06817-13B0-4937-ADE2-C34F8038BCD1} WinExe - TextReplacementApp - tra - v4.8 - 512 - true - true - - - - - false - - - - - - - true - bin\x64\Debug\ - DEBUG;TRACE - full - x64 - 7.3 - prompt - true - bin\x64\Debug\tra.xml - - - bin\x64\Release\ - TRACE - true - pdbonly - x64 - 7.3 - prompt - true - bin\x64\Release\tra.xml - - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - 7.3 - prompt - true - bin\x86\Debug\tra.xml - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - 7.3 - prompt - true - bin\x86\Release\tra.xml - - - newxylogix.ICO + net9.0-windows + enable + enable + true + - - ..\packages\AlphaFS.2.2.6\lib\net452\AlphaFS.dll - - - ..\packages\log4net.2.0.17\lib\net45\log4net.dll - - - ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll - - - ..\packages\PostSharp.Redist.2024.1.4\lib\net45\PostSharp.dll - - - ..\packages\PostSharp.Patterns.Aggregation.Redist.2024.1.4\lib\net45\PostSharp.Patterns.Aggregation.dll - - - ..\packages\PostSharp.Patterns.Common.Redist.2024.1.4\lib\net47\PostSharp.Patterns.Common.dll - - - ..\packages\PostSharp.Patterns.Diagnostics.Redist.2024.1.4\lib\net47\PostSharp.Patterns.Diagnostics.dll - - - ..\packages\PostSharp.Patterns.Diagnostics.Log4Net.2024.1.4\lib\net45\PostSharp.Patterns.Diagnostics.Backends.Log4Net.dll - - - ..\packages\PostSharp.Patterns.Model.Redist.2024.1.4\lib\net45\PostSharp.Patterns.Model.dll - - - ..\packages\PostSharp.Patterns.Threading.Redist.2024.1.4\lib\net45\PostSharp.Patterns.Threading.dll - - - - - - - + + - - - - - Form - - - MainWindow.cs - - - - Form - - - ProgressDialog.cs - - - - - MainWindow.cs - - - ProgressDialog.cs - Designer - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - True - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - + + + - - - - - - - - - - - {c7cb816d-0fb7-4c62-a225-98711e5537a5} - xyLOGIX.Files.MemoryMapped.Interfaces - - - {2557ED07-457A-415C-9C36-25C5ED163907} - xyLOGIX.Files.MemoryMapped - - - - - - - + - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - - - \ No newline at end of file + TextReplacementApp + Small Windows Forms utility that illustrates quickly replacing text in all files wihtin a certain directory tree, even if there are millions of them, very rapidly. This project is customized and adapted specifically for those directory trees that contain C# Solution(s) written using Visual Studio 2022 or higher. The code in this application is made freely available to copy, distribute, and/or reproduce in any medium, at any time, in any manner or format. + xyLOGIX, LLC + xyLOGIX Fast Text Replacer Tool + Copyright © 2024 by xyLOGIX, LLC. All rights given up freely. + This software component is a part of the xyLOGIX Fast Text Replacer Tool software system. Fast Text Replacer Tool is a trademark of xyLOGIX, LLC. All rights reserved, especially those granted by international treaties. All other trademarks appearing in this software system's code or its user interface belong to their respective owners. + + + diff --git a/TextReplacementApp/TextReplacementApp.csproj.BAK b/TextReplacementApp/TextReplacementApp.csproj.BAK deleted file mode 100644 index 72bd873..0000000 --- a/TextReplacementApp/TextReplacementApp.csproj.BAK +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - Debug - AnyCPU - {A4F06817-13B0-4937-ADE2-C34F8038BCD1} - WinExe - TextReplacementApp - TextReplacementApp - v4.8 - 512 - true - true - - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\AlphaFS.2.2.6\lib\net452\AlphaFS.dll - - - ..\packages\log4net.2.0.16\lib\net45\log4net.dll - - - ..\packages\PostSharp.Redist.2023.0.8\lib\net45\PostSharp.dll - - - ..\packages\PostSharp.Patterns.Aggregation.Redist.2023.0.8\lib\net45\PostSharp.Patterns.Aggregation.dll - - - ..\packages\PostSharp.Patterns.Common.Redist.2023.0.8\lib\net47\PostSharp.Patterns.Common.dll - - - ..\packages\PostSharp.Patterns.Diagnostics.Redist.2023.0.8\lib\net47\PostSharp.Patterns.Diagnostics.dll - - - ..\packages\PostSharp.Patterns.Diagnostics.Log4Net.2023.0.8\lib\net45\PostSharp.Patterns.Diagnostics.Backends.Log4Net.dll - - - ..\packages\PostSharp.Patterns.Model.Redist.2023.0.8\lib\net45\PostSharp.Patterns.Model.dll - - - ..\packages\PostSharp.Patterns.Threading.Redist.2023.0.8\lib\net45\PostSharp.Patterns.Threading.dll - - - - - - - - - - - - - - - - - - - Form - - - Form1.cs - - - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - - - \ No newline at end of file diff --git a/TextReplacementApp/packages.config b/TextReplacementApp/packages.config deleted file mode 100644 index 2c5d530..0000000 --- a/TextReplacementApp/packages.config +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TextReplacementConsoleApp/App.config b/TextReplacementConsoleApp/App.config deleted file mode 100644 index 97ac932..0000000 --- a/TextReplacementConsoleApp/App.config +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TextReplacementConsoleApp/Program.cs b/TextReplacementConsoleApp/Program.cs index 277d9e7..6b85ac5 100644 --- a/TextReplacementConsoleApp/Program.cs +++ b/TextReplacementConsoleApp/Program.cs @@ -1,302 +1,21 @@ -using System; -using System.IO; -using System.IO.MemoryMappedFiles; -using System.Linq; -using System.Text; -using System.Threading; -using Directory = Alphaleonis.Win32.Filesystem.Directory; -using File = Alphaleonis.Win32.Filesystem.File; -using Path = Alphaleonis.Win32.Filesystem.Path; +Console.Title = "Text Replacement Console App"; -namespace TextReplacementConsoleApp -{ - public static class ListExtensions - { - /// - /// Compares the object with the - /// provided, to see if any of the - /// is a match. - /// - /// Type of the object to be tested. - /// Source object to check. - /// - /// Object or objects that should be compared to value - /// with the method. - /// - /// - /// True if any of the equals the value; - /// false otherwise. - /// - public static bool IsAnyOf(this T value, params T[] testObjects) - => testObjects.Contains(value); - } +var directoryPath = args.Length > 0 ? args[0] : @"c:\temp"; +var searchText = args.Length > 1 ? args[1] : "FooBarFoo"; +var replaceText = args.Length > 2 ? args[2] : "FooBarFoo-1"; - /// - /// Defines the behaviors of the application. - /// - public static class Program - { - /// - /// The entry point of the application. - /// - [STAThread] - public static void Main() - { - Console.Title = "Text Replacement Console App"; +Console.WriteLine($""" + Searching all code in '{directoryPath}'. + Replacing '{searchText}' with '{replaceText}' ... + Start Time ..: {DateTime.UtcNow.ToLocalTime()} + """); - const string directoryPath = - @"C:\Users\Brian Hart\source\repos\astrohart\NuGetPackageAutoUpdater"; - const string searchText = "Foo"; - const string replaceText = "Bar"; +var res = Common.ReplaceTextInFiles(directoryPath, searchText, replaceText); - Console.WriteLine($"Searching all code in '{directoryPath}'..."); - Console.WriteLine( - $"Replacing '{searchText}' with '{replaceText}'..." - ); +Console.WriteLine($""" + End Time ....: {DateTime.UtcNow.ToLocalTime()} + Text replacement completed. + {res} + """); - Console.WriteLine($"Start Time: {DateTime.Now:O}"); - - ReplaceTextInFiles(directoryPath, searchText, replaceText); - - Console.WriteLine($"End Time: {DateTime.Now:O}"); - Console.WriteLine("Text replacement completed."); - - Console.ReadKey(); - } - - /// - /// Reads text from a memory-mapped file accessor. - /// - /// The memory-mapped file accessor. - /// The length of the text to read. - /// The text read from memory. - /// - /// This method reads text from a memory-mapped file accessor and returns it as a - /// string. - /// It reads the specified length of bytes from the accessor and decodes them using - /// UTF-8 encoding. - /// - private static string ReadTextFromMemory( - UnmanagedMemoryAccessor accessor, - long length - ) - { - // text contents of the file. - var result = string.Empty; - - try - { - // check for conditions that would prohibit our success - if (accessor == null) return result; - if (!accessor.CanRead) return result; - if (length <= 0L) return result; - - var bytes = new byte[length]; - accessor.ReadArray(0, bytes, 0, (int)length); - result = Encoding.UTF8.GetString(bytes); - } - catch - { - // If an exception was caught for any reason, then return the empty string - result = string.Empty; - } - - return result; - } - - /// - /// Replaces text in the specified file. - /// - /// The path of the file to perform text replacement on. - /// The text to search for in the file. - /// The text to replace the search text with. - /// - /// This method performs text replacement in the specified file. It reads - /// the content of the file, replaces occurrences of the search text with the - /// replace text, and writes the modified content back to the file. If the file - /// path contains specific directories (such as .git, .vs, etc.), it - /// skips the replacement. - /// - private static void ReplaceTextInFile( - string filePath, - string searchText, - string replaceText - ) - { - /* - * Account for this algorithm being run on a - * Visual Studio solution consisting only of - * C# projects, and in a local Git repo. - */ - - if (filePath.Contains(@"\.git\")) return; - if (filePath.Contains(@"\.vs\")) return; - if (filePath.Contains(@"\packages\")) return; - if (filePath.Contains(@"\bin\")) return; - if (filePath.Contains(@"\obj\")) return; - if (!Path.GetExtension(filePath) - .IsAnyOf( - ".txt", ".cs", ".resx", ".config", ".json", ".csproj", - ".settings", ".md" - )) - return; - - using (var fileStream = File.Open( - filePath, FileMode.Open, FileAccess.ReadWrite, - FileShare.None - )) - { - var originalLength = fileStream.Length; - - // If the original file length is zero, return early - if (originalLength == 0) return; - - using (var mmf = MemoryMappedFile.CreateFromFile( - fileStream, null, originalLength, - MemoryMappedFileAccess.ReadWrite, - HandleInheritability.None, false - )) - { - using (var accessor = mmf.CreateViewAccessor( - 0, originalLength, - MemoryMappedFileAccess.ReadWrite - )) - { - // Read the content from memory. If no text was obtained, stop. - var text = ReadTextFromMemory(accessor, originalLength); - if (string.IsNullOrWhiteSpace(text)) - return; - - // Perform text replacement - text = text.Replace(searchText, replaceText); - - // Calculate the length of the modified text - long modifiedLength = Encoding.UTF8.GetByteCount(text); - - // If the modified text is larger, extend the file size - if (modifiedLength > originalLength) - { - fileStream.SetLength(modifiedLength); - - // Re-open the file stream after extending the size - fileStream.Seek(0, SeekOrigin.Begin); - using (var newMmf = MemoryMappedFile.CreateFromFile( - fileStream, null, modifiedLength, - MemoryMappedFileAccess.ReadWrite, - HandleInheritability.None, false - )) - { - using (var newAccessor = - newMmf.CreateViewAccessor( - 0, modifiedLength, - MemoryMappedFileAccess.ReadWrite - )) - - // Write the modified content back to memory - WriteTextToMemory(newAccessor, text); - } - } - else - { - // Write the modified content back to memory - WriteTextToMemory(accessor, text); - } - } - } - } - } - - /// - /// Replaces text in all files within the specified directory and its - /// subdirectories. - /// - /// The path of the directory to search for files. - /// The text to search for in each file. - /// The text to replace the search text with. - /// - /// This method recursively searches for files within the specified - /// directory and its subdirectories. For each file found, it calls - /// to perform text replacement. Certain - /// directories (e.g., .git, .vs, etc.) are excluded from text - /// replacement. - /// - private static void ReplaceTextInFiles( - string directoryPath, - string searchText, - string replaceText - ) - { - try - { - if (!Directory.Exists(directoryPath)) - { - Console.WriteLine( - $"ERROR: The folder '{directoryPath}' was not found on the file system." - ); - return; - } - - var files = Directory.EnumerateFiles( - directoryPath, "*", - SearchOption.AllDirectories - ) - .Where( - file => !file.Contains(@"\.git\") && - !file.Contains(@"\.vs\") && - !file.Contains( - @"\packages\" - ) && - !file.Contains(@"\bin\") && - !file.Contains(@"\obj\") - ) - .ToList(); - - var completedFiles = 0; - - foreach (var file in files) - { - ReplaceTextInFile(file, searchText, replaceText); - Interlocked.Increment(ref completedFiles); - } - } - catch (Exception ex) - { - Console.WriteLine($"ERROR: {ex.Message}"); - } - } - - /// - /// Writes the specified text to a memory-mapped view accessor. - /// - /// The memory-mapped view accessor to write to. - /// The text to write. - /// - /// This method writes the UTF-8 encoded bytes of the - /// to the memory-mapped view accessor starting at the - /// beginning (offset zero). It is the responsibility of the caller to ensure that - /// the - /// length of the text matches the length of the memory-mapped view accessor. - /// - private static void WriteTextToMemory( - UnmanagedMemoryAccessor accessor, - string text - ) - { - try - { - // check for conditions that would prohibit our success - if (accessor == null) return; - if (!accessor.CanWrite) return; - if (string.IsNullOrWhiteSpace(text)) return; - - var bytes = Encoding.UTF8.GetBytes(text); - accessor.WriteArray(0, bytes, 0, bytes.Length); - } - catch (Exception ex) - { - // write the exception information to the console - Console.WriteLine($"ERROR: {ex.Message}"); - } - } - } -} \ No newline at end of file +// Console.ReadKey(); diff --git a/TextReplacementConsoleApp/Properties/AssemblyInfo.cs b/TextReplacementConsoleApp/Properties/AssemblyInfo.cs deleted file mode 100644 index 90e523f..0000000 --- a/TextReplacementConsoleApp/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TextReplacementConsoleApp")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("xyLOGIX, LLC")] -[assembly: AssemblyProduct("TextReplacementConsoleApp")] -[assembly: AssemblyCopyright("Copyright © xyLOGIX, LLC 2024")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("69a9abd0-bed3-4370-9cbf-55f6df36cdd7")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TextReplacementConsoleApp/TextReplacementConsoleApp.csproj b/TextReplacementConsoleApp/TextReplacementConsoleApp.csproj index 6176e7d..ddbffe1 100644 --- a/TextReplacementConsoleApp/TextReplacementConsoleApp.csproj +++ b/TextReplacementConsoleApp/TextReplacementConsoleApp.csproj @@ -1,113 +1,24 @@ - - - - + + - Debug - AnyCPU - {69A9ABD0-BED3-4370-9CBF-55F6DF36CDD7} Exe - TextReplacementConsoleApp - trca - v4.8 - 512 - true - true - - + net9.0-windows + enable + enable - - true - bin\x64\Debug\ - DEBUG;TRACE - full - x64 - 7.3 - prompt - true - - - bin\x64\Release\ - TRACE - true - pdbonly - x64 - 7.3 - prompt - true - - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - 7.3 - prompt - true - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - 7.3 - prompt - true - - - - ..\packages\AlphaFS.2.2.6\lib\net452\AlphaFS.dll - - - ..\packages\log4net.2.0.17\lib\net45\log4net.dll - - - ..\packages\PostSharp.Redist.2024.1.4\lib\net45\PostSharp.dll - - - ..\packages\PostSharp.Patterns.Aggregation.Redist.2024.1.4\lib\net45\PostSharp.Patterns.Aggregation.dll - - - ..\packages\PostSharp.Patterns.Common.Redist.2024.1.4\lib\net47\PostSharp.Patterns.Common.dll - - - ..\packages\PostSharp.Patterns.Diagnostics.Redist.2024.1.4\lib\net47\PostSharp.Patterns.Diagnostics.dll - - - ..\packages\PostSharp.Patterns.Diagnostics.Log4Net.2024.1.4\lib\net45\PostSharp.Patterns.Diagnostics.Backends.Log4Net.dll - - - ..\packages\PostSharp.Patterns.Model.Redist.2024.1.4\lib\net45\PostSharp.Patterns.Model.dll - - - ..\packages\PostSharp.Patterns.Threading.Redist.2024.1.4\lib\net45\PostSharp.Patterns.Threading.dll - - - - - - - - - - + - - + + - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - - - \ No newline at end of file + + + TextReplacementConsoleApp + Small Windows Forms utility that illustrates quickly replacing text in all files wihtin a certain directory tree, even if there are millions of them, very rapidly. This project is customized and adapted specifically for those directory trees that contain C# Solution(s) written using Visual Studio 2022 or higher. The code in this application is made freely available to copy, distribute, and/or reproduce in any medium, at any time, in any manner or format. + xyLOGIX, LLC + xyLOGIX Fast Text Replacer Tool + Copyright © 2024 by xyLOGIX, LLC. All rights given up freely. + This software component is a part of the xyLOGIX Fast Text Replacer Tool software system. Fast Text Replacer Tool is a trademark of xyLOGIX, LLC. All rights reserved, especially those granted by international treaties. All other trademarks appearing in this software system's code or its user interface belong to their respective owners. + + + diff --git a/TextReplacementConsoleApp/packages.config b/TextReplacementConsoleApp/packages.config deleted file mode 100644 index 26d57f3..0000000 --- a/TextReplacementConsoleApp/packages.config +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TextReplacementConsoleDemos/Program.cs b/TextReplacementConsoleDemos/Program.cs new file mode 100644 index 0000000..41f779e --- /dev/null +++ b/TextReplacementConsoleDemos/Program.cs @@ -0,0 +1,93 @@ +Console.Title = "Text Replacement Console App"; + +var directoryPath = args.Length > 0 ? args[0] : @"c:\temp"; +var searchText = args.Length > 1 ? args[1] : "FooBarFoo"; +var replaceText = args.Length > 2 ? args[2] : "FooBarFoo-1"; + +Console.WriteLine($""" + Searching all code in '{directoryPath}'. + Replacing '{searchText}' with '{replaceText}' ... + Start Time ..: {DateTime.UtcNow.ToLocalTime()} + """); + +var res = Common.ReplaceTextInFiles(directoryPath, searchText, replaceText); + +Console.WriteLine($""" + End Time ....: {DateTime.UtcNow.ToLocalTime()} + Text replacement completed. + {res} + """); + +// Console.ReadKey(); + +#if true +{ + Console.WriteLine("\n/* Simple test of 'ReplaceTextInFile()'. */\n"); // <== + + if (!Directory.Exists(directoryPath)) return; + + var files = Directory.GetFiles(directoryPath, "*", SearchOption.AllDirectories); + var processed = 0; + + foreach (var file in files.Where(file => Common.ReplaceTextInFile(file, searchText, replaceText))) + { + Console.WriteLine($"Processed: '{file}'."); + processed++; + } + + Console.WriteLine($""" + Files Total: {files.Length:n0} + Processed: {processed:n0} + + Replaced == Processed ('{res.ReplacedFiles == processed}') + """); +} +#endif + +#if true +{ + Console.WriteLine("\n/* Simple test of 'ProgressDialog()'. */\n"); // <== + + using var progressDialog = new ProgressDialog(); + + // Use Progress to report progress. + var progressReporter = new Progress(report + => progressDialog.UpdateProgress(report.CurrentFile, report.ProgressPercentage)); + + // Start a new task for text replacement. + _ = Task.Run(() => + { + _ = Common.ReplaceTextInFiles(directoryPath, searchText, replaceText, progressReporter); + + // Close the progress dialog. + // if (InvokeRequired) + _ = progressDialog.BeginInvoke(new MethodInvoker(progressDialog.Close)); + // else progressDialog.Close(); + + // Show completion message. + // _ = MessageBox.Show("Text replacement completed.", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information); + }); + + _ = progressDialog.ShowDialog(); +} +#endif + +#if false +{ + Console.WriteLine("\n/* Simple test of 'ReplaceTextInFile()'. */\n"); // <== + + var files = Directory.EnumerateFiles(@"c:\temp", "olle.txt", SearchOption.AllDirectories).ToArray(); + var processed = 0; + + foreach (var file in files.Where(file => Common.ReplaceTextInFile(file, "kalle", "pelle", skipCheck: true))) + { + Console.WriteLine($"Processed: '{file}'."); + processed++; + } + + Console.WriteLine($""" + Files Total: {files.Length:n0} + Processed: {processed:n0} + """); +} +#endif diff --git a/TextReplacementConsoleDemos/Properties/launchSettings.json b/TextReplacementConsoleDemos/Properties/launchSettings.json new file mode 100644 index 0000000..80bca28 --- /dev/null +++ b/TextReplacementConsoleDemos/Properties/launchSettings.json @@ -0,0 +1,15 @@ +{ + "profiles": { + "TextReplacementConsoleDemos": { + "commandName": "Project" + }, + "TextReplacementConsoleDemos - clones": { + "commandName": "Project", + "commandLineArgs": "\\repos\\clones foofoofoo foofoofoo-1" + }, + "TextReplacementConsoleDemos - fork": { + "commandName": "Project", + "commandLineArgs": "\\repos\\fork foofoofoo foofoofoo-1" + } + } +} \ No newline at end of file diff --git a/TextReplacementConsoleDemos/TextReplacementConsoleDemos.csproj b/TextReplacementConsoleDemos/TextReplacementConsoleDemos.csproj new file mode 100644 index 0000000..c7c7d86 --- /dev/null +++ b/TextReplacementConsoleDemos/TextReplacementConsoleDemos.csproj @@ -0,0 +1,16 @@ + + + + Exe + net9.0-windows + enable + enable + + + + + + + + + diff --git a/TextReplacementShared/Common.cs b/TextReplacementShared/Common.cs new file mode 100644 index 0000000..8e516e5 --- /dev/null +++ b/TextReplacementShared/Common.cs @@ -0,0 +1,182 @@ +#define UseArray + +namespace TextReplacementShared; + +public sealed class Common +{ + Common() { /* Utility classes should not have public constructors. */ } + + /* Account for this algorithm being run on a Visual Studio solution consisting only of C# projects, and in a local Git repo. */ + + static readonly string[] ExclusionsArr = [".git", ".vs", ".vscode", "bin", "obj", "packages"]; // Consider the order... + static readonly string[] InclusionsArr = [".config", ".cs", ".csproj", ".json", ".md", ".resx", ".settings", ".txt"]; // Consider the order... + + static readonly SearchValues Exclusions = SearchValues.Create(ExclusionsArr, StringComparison.OrdinalIgnoreCase); + static readonly SearchValues Inclusions = SearchValues.Create(InclusionsArr, StringComparison.OrdinalIgnoreCase); + + static IWin32Window? s_owner; // Determine whether invoked by a Forms application or a Console application. + + /// Replaces text in all files within the specified directory and its subdirectories. + /// The path of the directory to search for files. + /// The text to search for in each file. + /// The text to replace the search text with. + /// An object for reporting progress during text replacement. + /// An implementation of IWin32Window that will own the modal dialog box. + /// + /// This method recursively searches for files within the specified directory and its subdirectories.
+ /// For each file found, it calls to perform text replacement.
+ /// Progress is reported using the specified .
+ /// Certain directories (e.g., .git, .vs, etc.) are excluded from text replacement. + ///
+ /// (TotalFiles, CompletedFiles, ReplacedFiles) - Total number of files, number of processed files and number of replaced files. + public static ReplaceResult ReplaceTextInFiles(in string directoryPath, in string searchText, in string replaceText, IProgress? progressReporter = null, IWin32Window? owner = null) + { + var sw = Stopwatch.StartNew(); + var totalFiles = 0d; // (double) for 'progressPercentage'. + var completedFiles = 0; + var replacedFiles = 0; + var minLength = searchText.Length; // If the original file length is less than 'searchText', skip the file. + + try + { + s_owner = owner; + + var files = new FileSystemEnumerable( + directory: directoryPath, + transform: (ref FileSystemEntry entry) => entry.ToSpecifiedFullPath(), + options: new EnumerationOptions { AttributesToSkip = FileAttributes.None, RecurseSubdirectories = true }) + { + ShouldRecursePredicate = (ref FileSystemEntry entry) => !entry.Attributes.HasFlag(FileAttributes.ReparsePoint) && !Exclusions.Contains(entry.FileName.ToString()), + ShouldIncludePredicate = (ref FileSystemEntry entry) => !entry.IsDirectory && Inclusions.Contains(Path.GetExtension(entry.FileName).ToString()) && entry.Length >= minLength + } + .ToList(); // For 'totalFiles'. + + totalFiles = files.Count; + + foreach (var file in files) + { + if (ReplaceTextInFile(file, searchText, replaceText, skipCheck: true)) _ = Interlocked.Increment(ref replacedFiles); + _ = Interlocked.Increment(ref completedFiles); + + // Report progress (if not 'null'). + progressReporter?.Report(new ProgressReport(file, (int)(completedFiles / totalFiles * 100))); + } + } + catch (Exception ex) { DisplayException(ex); } // Display an alert with the exception text. + + return new((int)totalFiles, completedFiles, replacedFiles, sw.ElapsedMilliseconds); // Finally.. + } + + /// Replaces text in the specified file. + /// The path of the file to perform text replacement on. + /// The text to search for in the file. + /// The text to replace the search text with. + /// Defaults to false - Performs some validation of directory and extension. + /// + /// This method performs text replacement in the specified file. It reads the content of the file,
+ /// replaces occurrences of the search text with the replace text, and writes the modified content back to the file.
+ /// If is false check if the file path contains specific directories (such as .git, .vs, etc.), in that case it skips the replacement. + ///
+ /// Returns true if the file has been processed, else false. + public static bool ReplaceTextInFile(in string filePath, in string searchText, in string replaceText, bool skipCheck = false) + { + try + { + if (!skipCheck && SkipFile(filePath)) return false; // Don't like the naming but want default to be false... + + using var fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None); + + var originalLength = fileStream.Length; + if (!skipCheck && originalLength < searchText.Length) return false; // If the original file length is less than 'searchText', return early. + + var originalText = ReadTextFromMemory(fileStream, originalLength); // Read the content from memory. + if (string.IsNullOrWhiteSpace(originalText)) return false; + + var modifiedText = originalText.Replace(searchText, replaceText, StringComparison.Ordinal); // Perform text replacement. + + if (modifiedText.Equals(originalText, StringComparison.Ordinal)) return false; // File not modified. + // Debug.WriteLine($"CHANGED - {filePath}"); + + var modifiedLength = searchText.Length == replaceText.Length + ? originalLength + : Encoding.UTF8.GetByteCount(modifiedText); // Calculate the length of the modified text. + + if (modifiedLength != originalLength) // The modified text has changed the length, set the new file size. + { + fileStream.SetLength(modifiedLength); + _ = fileStream.Seek(0, SeekOrigin.Begin); // Re-open the file stream after changing the size. + } + + return WriteTextToMemory(fileStream, modifiedLength, modifiedText); // Write the modified content back to memory. + } + catch (Exception ex) { DisplayException(ex); return false; } // Display an alert with the exception text. + +#if !UseArray + static bool SkipFile(string filePath) + => !InclusionsArr.Contains(Path.GetExtension(filePath), StringComparer.OrdinalIgnoreCase) || // Cheapest... + Array.Exists(ExclusionsArr, x => filePath.Contains($@"\{x}\", StringComparison.OrdinalIgnoreCase)); +#else + static bool SkipFile(in string filePath) + => !Inclusions.Contains(Path.GetExtension(filePath)) || // Cheapest... + filePath.Split('\\').SkipLast(1).Any(static x => Exclusions.Contains(x)); // Hmm.. not pleasant. +#endif + } + + /// Reads text from a memory-mapped file accessor. + /// An open to memory-map. + /// The length of the text to read. + /// + /// This method reads text from a memory-mapped file accessor and returns it as a string.
+ /// It reads the specified length of bytes from the accessor and decodes them using UTF-8 encoding. + ///
+ /// The text read from memory. + static string ReadTextFromMemory(FileStream fileStream, long length) + { + try // Check for conditions that would prohibit our success. + { + using var mmf = MemoryMappedFile.CreateFromFile(fileStream, mapName: null, length, MemoryMappedFileAccess.Read, HandleInheritability.None, leaveOpen: true); + using var accessor = mmf.CreateViewAccessor(0, length, MemoryMappedFileAccess.Read); + + // if (!accessor.CanRead || length <= 0) return string.Empty; // Redundant... + + var bytes = new byte[length]; + _ = accessor.ReadArray(0, bytes, 0, (int)length); + + return Encoding.UTF8.GetString(bytes); // Decode UTF8. + } + catch (Exception ex) { DisplayException(ex); return string.Empty; } // Display an alert with the exception text. + } + + /// Writes the specified text to a memory-mapped view accessor. + /// The to memory-map. + /// The length of the text to write. + /// The text to write. + /// + /// This method writes the UTF-8 encoded bytes of the to the memory-mapped view accessor + /// starting at the beginning (offset zero).
It is the responsibility of the caller to ensure that the + /// length of the text matches the length of the memory-mapped view accessor. + ///
+ static bool WriteTextToMemory(FileStream fileStream, long length, in string text) + { + try // Check for conditions that would prohibit our success. + { + // if (fileStream is null || string.IsNullOrWhiteSpace(text)) return false; // Rely on exception with a message, what if text really is empty... + + using var mmf = MemoryMappedFile.CreateFromFile(fileStream, mapName: null, length, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, leaveOpen: false); + using var accessor = mmf.CreateViewAccessor(0, length, MemoryMappedFileAccess.ReadWrite); + + var bytes = Encoding.UTF8.GetBytes(text); + accessor.WriteArray(0, bytes, 0, bytes.Length); + + return true; + } + catch (Exception ex) { DisplayException(ex); return false; } // Display an alert with the exception text. + } + + public static void DisplayException(Exception ex) + { + if (s_owner is null) Console.WriteLine($"\n**ERROR: {ex.Message}\n"); // Write the exception information to the console. + else + _ = MessageBox.Show(s_owner, ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Stop); + } +} diff --git a/TextReplacementShared/ListExtensions.cs b/TextReplacementShared/ListExtensions.cs new file mode 100644 index 0000000..3de7b47 --- /dev/null +++ b/TextReplacementShared/ListExtensions.cs @@ -0,0 +1,15 @@ +namespace TextReplacementShared; + +/// Exposes static extension methods for checking the contents of a list. +public static class ListExtensions +{ + /// + /// Compares the object with the provided, + /// to see if any of the is a match. + /// + /// Type of the object to be tested. + /// Source object to check. + /// Object or objects that should be compared to value with the method. + /// True if any of the equals the value; false otherwise. + public static bool IsAnyOf(this T value, params T[] testObjects) => testObjects.Contains(value); +} diff --git a/TextReplacementApp/ProgressDialog.Designer.cs b/TextReplacementShared/ProgressDialog.Designer.cs similarity index 89% rename from TextReplacementApp/ProgressDialog.Designer.cs rename to TextReplacementShared/ProgressDialog.Designer.cs index 2322f94..ed24ce0 100644 --- a/TextReplacementApp/ProgressDialog.Designer.cs +++ b/TextReplacementShared/ProgressDialog.Designer.cs @@ -1,22 +1,15 @@ -namespace TextReplacementApp +namespace TextReplacementShared { partial class ProgressDialog { - /// - /// Required designer variable. - /// + /// Required designer variable. private System.ComponentModel.IContainer components = null; - /// - /// Clean up any resources being used. - /// + /// Clean up any resources being used. /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { - if (disposing && (components != null)) - { - components.Dispose(); - } + if (disposing) components?.Dispose(); base.Dispose(disposing); } diff --git a/TextReplacementShared/ProgressDialog.cs b/TextReplacementShared/ProgressDialog.cs new file mode 100644 index 0000000..a34cfa4 --- /dev/null +++ b/TextReplacementShared/ProgressDialog.cs @@ -0,0 +1,37 @@ +namespace TextReplacementShared; + +/// Represents a dialog used to display the progress of an operation. +/// +/// The class provides a simple dialog for displaying progress information during long-running operations.
+/// It contains a label to show the current file being processed and a progress bar to indicate the overall progress of the operation. +///
+public partial class ProgressDialog : Form +{ + /// Initializes a new instance of the class. + public ProgressDialog() => InitializeComponent(); + + /// Updates the progress of the operation being displayed in the progress dialog. + /// The path of the current file being processed. + /// The percentage of completion of the operation. + /// + /// This method updates the text displayed for the current file being processed and adjusts the progress bar to reflect the progress percentage.
+ /// If invoked from a different thread than the one that created the control, it will use to marshal the call to the proper thread. + ///
+ public void UpdateProgress(string filePath, int progressPercentage) + { + if (InvokeRequired) Invoke(new Action(() => UpdateProgress(filePath, progressPercentage))); + else + { + lblFilePath.Text = filePath; + progressBar.Value = progressPercentage; + } + } + + /// Raises the event. + /// An that contains the event data. + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + Text = Application.ProductName; + } +} diff --git a/TextReplacementApp/ProgressDialog.resx b/TextReplacementShared/ProgressDialog.resx similarity index 97% rename from TextReplacementApp/ProgressDialog.resx rename to TextReplacementShared/ProgressDialog.resx index 0673ef8..cbac426 100644 --- a/TextReplacementApp/ProgressDialog.resx +++ b/TextReplacementShared/ProgressDialog.resx @@ -1,4 +1,4 @@ - + - + - + - - - - + + + + - - + + - - + + - - - - + + + + - + - + @@ -117,7 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + AAABAAMAEBAAAAEAIABoBAAANgAAACAgAAABACAAqBAAAJ4EAAAwMAAAAQAgAKglAABGFQAAKAAAABAA diff --git a/TextReplacementShared/ProgressReport.cs b/TextReplacementShared/ProgressReport.cs new file mode 100644 index 0000000..14e33cf --- /dev/null +++ b/TextReplacementShared/ProgressReport.cs @@ -0,0 +1,15 @@ +namespace TextReplacementShared; + +/// Represents a progress report containing information about the current file being processed and the progress percentage. +/// Initializes a new instance of the class with the specified current file and progress percentage. +/// The path of the current file being processed. +/// The progress percentage of the operation. +public class ProgressReport(string currentFile, int progressPercentage) +{ + /// Gets the path of the current file being processed. + public string CurrentFile { get; } = $"{(currentFile.Length <= 59 ? currentFile : $"{currentFile[..29]}…{currentFile[^29..]}")}"; + // public string CurrentFile { get; } = currentFile; + + /// Gets the progress percentage of the operation. + public int ProgressPercentage { get; } = progressPercentage; +} diff --git a/TextReplacementShared/ReplaceResult.cs b/TextReplacementShared/ReplaceResult.cs new file mode 100644 index 0000000..02011db --- /dev/null +++ b/TextReplacementShared/ReplaceResult.cs @@ -0,0 +1,17 @@ +namespace TextReplacementShared; + +/// Total number of files, number of processed files and number of replaced files. +/// Total number of files. +/// Number of processed files. +/// Number of replaced files. +/// The total elapsed time measured by the current instance, in milliseconds. +public record ReplaceResult(int TotalFiles, int CompletedFiles, int ReplacedFiles, long ElapsedMilliseconds) +{ + public override string ToString() => $""" + Elapsed time {ElapsedMilliseconds:n0} ms. + + Files Total: {TotalFiles:n0} + Completed: {CompletedFiles:n0} + Replaced: {ReplacedFiles:n0} + """; +} diff --git a/TextReplacementShared/TextReplacementShared.csproj b/TextReplacementShared/TextReplacementShared.csproj new file mode 100644 index 0000000..10e5b73 --- /dev/null +++ b/TextReplacementShared/TextReplacementShared.csproj @@ -0,0 +1,19 @@ + + + + Library + net9.0-windows + enable + enable + true + + + + + + + + + + +