aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Converters/FixupCommandBase.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Converters/FixupCommandBase.cs')
-rw-r--r--src/WixToolset.Converters/FixupCommandBase.cs267
1 files changed, 267 insertions, 0 deletions
diff --git a/src/WixToolset.Converters/FixupCommandBase.cs b/src/WixToolset.Converters/FixupCommandBase.cs
new file mode 100644
index 00000000..0f58fbdb
--- /dev/null
+++ b/src/WixToolset.Converters/FixupCommandBase.cs
@@ -0,0 +1,267 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Converters
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Threading;
9 using System.Threading.Tasks;
10 using System.Xml;
11 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services;
13
14 internal abstract class FixupCommandBase : ICommandLineCommand
15 {
16 protected FixupCommandBase()
17 {
18 this.IndentationAmount = 4; // default indentation amount
19 this.ErrorsAsWarnings = new HashSet<string>();
20 this.ExemptFiles = new HashSet<string>();
21 this.IgnoreErrors = new HashSet<string>();
22 this.SearchPatternResults = new HashSet<string>();
23 this.SearchPatterns = new List<string>();
24 }
25
26 public bool ShowLogo { get; private set; }
27
28 public bool StopParsing { get; private set; }
29
30 protected bool ShowHelp { get; set; }
31
32 protected bool DryRun { get; set; }
33
34 protected HashSet<string> ErrorsAsWarnings { get; }
35
36 protected HashSet<string> IgnoreErrors { get; }
37
38 protected HashSet<string> ExemptFiles { get; }
39
40 protected int IndentationAmount { get; set; }
41
42 protected bool Recurse { get; set; }
43
44 private HashSet<string> SearchPatternResults { get; }
45
46 private List<string> SearchPatterns { get; }
47
48 private string SettingsFile1 { get; set; }
49
50 private string SettingsFile2 { get; set; }
51
52 public bool TryParseArgument(ICommandLineParser parser, string argument)
53 {
54 if (!parser.IsSwitch(argument))
55 {
56 this.SearchPatterns.Add(argument);
57 return true;
58 }
59
60 var parameter = argument.Substring(1);
61 switch (parameter.ToLowerInvariant())
62 {
63 case "?":
64 case "h":
65 case "-help":
66 this.ShowHelp = true;
67 this.ShowLogo = true;
68 this.StopParsing = true;
69 return true;
70
71 case "n":
72 case "--dry-run":
73 this.DryRun = true;
74 return true;
75
76 case "nologo":
77 case "-nologo":
78 this.ShowLogo = false;
79 return true;
80
81 case "s":
82 case "r":
83 case "-recurse":
84 this.Recurse = true;
85 return true;
86
87 default: // other parameters
88 if (parameter.StartsWith("set1", StringComparison.Ordinal))
89 {
90 this.SettingsFile1 = parameter.Substring(4);
91 return true;
92 }
93 else if (parameter.StartsWith("set2", StringComparison.Ordinal))
94 {
95 this.SettingsFile2 = parameter.Substring(4);
96 return true;
97 }
98 else if (parameter.StartsWith("indent:", StringComparison.Ordinal))
99 {
100 try
101 {
102 this.IndentationAmount = Convert.ToInt32(parameter.Substring(7));
103 }
104 catch
105 {
106 parser.ErrorArgument = parameter; // $"Invalid numeric argument: {parameter}";
107 }
108 return true;
109 }
110
111 return false;
112 }
113 }
114
115 public abstract Task<int> ExecuteAsync(CancellationToken cancellationToken);
116
117 protected void ParseSettings(string defaultSettingsFile)
118 {
119 // parse the settings if any were specified
120 if (null != this.SettingsFile1 || null != this.SettingsFile2)
121 {
122 this.ParseSettingsFiles(this.SettingsFile1, this.SettingsFile2);
123 }
124 else
125 {
126 if (File.Exists(defaultSettingsFile))
127 {
128 this.ParseSettingsFiles(defaultSettingsFile, null);
129 }
130 }
131 }
132
133 protected int Inspect(Func<string, bool, int> inspector, CancellationToken cancellationToken)
134 {
135 var errors = this.InspectSubDirectories(inspector, Path.GetFullPath("."), cancellationToken);
136
137 foreach (var searchPattern in this.SearchPatterns)
138 {
139 if (!this.SearchPatternResults.Contains(searchPattern))
140 {
141 Console.Error.WriteLine("Could not find file \"{0}\"", searchPattern);
142 errors++;
143 }
144 }
145
146 return errors;
147 }
148
149 /// <summary>
150 /// Inspect sub-directories.
151 /// </summary>
152 /// <param name="directory">The directory whose sub-directories will be inspected.</param>
153 /// <returns>The number of errors that were found.</returns>
154 private int InspectSubDirectories(Func<string, bool, int> inspector, string directory, CancellationToken cancellationToken)
155 {
156 var errors = 0;
157
158 foreach (var searchPattern in this.SearchPatterns)
159 {
160 foreach (var sourceFilePath in GetFiles(directory, searchPattern))
161 {
162 cancellationToken.ThrowIfCancellationRequested();
163
164 var file = new FileInfo(sourceFilePath);
165
166 if (!this.ExemptFiles.Contains(file.Name.ToUpperInvariant()))
167 {
168 this.SearchPatternResults.Add(searchPattern);
169 errors += inspector(file.FullName, !this.DryRun);
170 }
171 }
172 }
173
174 if (this.Recurse)
175 {
176 foreach (var childDirectoryPath in Directory.GetDirectories(directory))
177 {
178 errors += this.InspectSubDirectories(inspector, childDirectoryPath, cancellationToken);
179 }
180 }
181
182 return errors;
183 }
184
185 /// <summary>
186 /// Parse the primary and secondary settings files.
187 /// </summary>
188 /// <param name="localSettingsFile1">The primary settings file.</param>
189 /// <param name="localSettingsFile2">The secondary settings file.</param>
190 private void ParseSettingsFiles(string localSettingsFile1, string localSettingsFile2)
191 {
192 if (null == localSettingsFile1 && null != localSettingsFile2)
193 {
194 throw new ArgumentException("Cannot specify a secondary settings file (set2) without a primary settings file (set1).", nameof(localSettingsFile2));
195 }
196
197 var settingsFile = localSettingsFile1;
198 while (null != settingsFile)
199 {
200 var doc = new XmlDocument();
201 doc.Load(settingsFile);
202
203 // get the types of tests that will have their errors displayed as warnings
204 var testsIgnoredElements = doc.SelectNodes("/Settings/IgnoreErrors/Test");
205 foreach (XmlElement test in testsIgnoredElements)
206 {
207 var key = test.GetAttribute("Id");
208 this.IgnoreErrors.Add(key);
209 }
210
211 // get the types of tests that will have their errors displayed as warnings
212 var testsAsWarningsElements = doc.SelectNodes("/Settings/ErrorsAsWarnings/Test");
213 foreach (XmlElement test in testsAsWarningsElements)
214 {
215 var key = test.GetAttribute("Id");
216 this.ErrorsAsWarnings.Add(key);
217 }
218
219 // get the exempt files
220 var localExemptFiles = doc.SelectNodes("/Settings/ExemptFiles/File");
221 foreach (XmlElement file in localExemptFiles)
222 {
223 var key = file.GetAttribute("Name").ToUpperInvariant();
224 this.ExemptFiles.Add(key);
225 }
226
227 settingsFile = localSettingsFile2;
228 localSettingsFile2 = null;
229 }
230 }
231
232 /// <summary>
233 /// Get the files that match a search path pattern.
234 /// </summary>
235 /// <param name="baseDir">The base directory at which to begin the search.</param>
236 /// <param name="searchPath">The search path pattern.</param>
237 /// <returns>The files matching the pattern.</returns>
238 private static string[] GetFiles(string baseDir, string searchPath)
239 {
240 // convert alternate directory separators to the standard one
241 var filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
242 var lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
243 string[] files = null;
244
245 try
246 {
247 if (0 > lastSeparator)
248 {
249 files = Directory.GetFiles(baseDir, filePath);
250 }
251 else // found directory separator
252 {
253 var searchPattern = filePath.Substring(lastSeparator + 1);
254
255 files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), searchPattern);
256 }
257 }
258 catch (DirectoryNotFoundException)
259 {
260 // don't let this function throw the DirectoryNotFoundException. (this exception
261 // occurs for non-existant directories and invalid characters in the searchPattern)
262 }
263
264 return files;
265 }
266 }
267}