diff options
Diffstat (limited to 'src/WixToolset.Converters/FixupCommandBase.cs')
-rw-r--r-- | src/WixToolset.Converters/FixupCommandBase.cs | 267 |
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 | |||
3 | namespace 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 | } | ||