main
cs 271 lines 10.1 KB
Raw
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.Xml;
10 using WixToolset.Data;
11 using WixToolset.Extensibility;
12 using WixToolset.Extensibility.Services;
13
14 internal abstract class FixupCommandBase : BaseCommandLineCommand
15 {
16 protected FixupCommandBase(IServiceProvider serviceProvider)
17 {
18 this.Messaging = serviceProvider.GetService<IMessaging>();
19
20 this.IndentationAmount = 4; // default indentation amount
21 this.ErrorsAsWarnings = new HashSet<string>();
22 this.ExemptFiles = new HashSet<string>();
23 this.IgnoreErrors = new HashSet<string>();
24 this.SearchPatternResults = new HashSet<string>();
25 this.SearchPatterns = new List<string>();
26 }
27
28 protected IMessaging Messaging { get; }
29
30 protected CustomTableTarget CustomTableSetting { 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 override 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 "-custom-table":
64 var customTableSetting = parser.GetNextArgumentOrError(argument);
65 switch (customTableSetting)
66 {
67 case "bundle":
68 this.CustomTableSetting = CustomTableTarget.Bundle;
69 break;
70 case "msi":
71 this.CustomTableSetting = CustomTableTarget.Msi;
72 break;
73 default:
74 parser.ReportErrorArgument(argument);
75 break;
76 }
77 return true;
78
79 case "n":
80 case "-dry-run":
81 this.DryRun = true;
82 return true;
83
84 case "s":
85 case "r":
86 case "-recurse":
87 case "-recursive":
88 this.Recurse = true;
89 return true;
90
91 default: // other parameters
92 if (parameter.StartsWith("set1", StringComparison.Ordinal))
93 {
94 this.SettingsFile1 = parameter.Substring(4);
95 return true;
96 }
97 else if (parameter.StartsWith("set2", StringComparison.Ordinal))
98 {
99 this.SettingsFile2 = parameter.Substring(4);
100 return true;
101 }
102 else if (parameter.StartsWith("indent:", StringComparison.Ordinal))
103 {
104 try
105 {
106 this.IndentationAmount = Convert.ToInt32(parameter.Substring(7));
107 }
108 catch
109 {
110 parser.ReportErrorArgument(parameter); // $"Invalid numeric argument: {parameter}";
111 }
112 return true;
113 }
114
115 return false;
116 }
117 }
118
119 protected void ParseSettings(string defaultSettingsFile)
120 {
121 // parse the settings if any were specified
122 if (null != this.SettingsFile1 || null != this.SettingsFile2)
123 {
124 this.ParseSettingsFiles(this.SettingsFile1, this.SettingsFile2);
125 }
126 else
127 {
128 if (File.Exists(defaultSettingsFile))
129 {
130 this.ParseSettingsFiles(defaultSettingsFile, null);
131 }
132 }
133 }
134
135 protected int Inspect(Func<string, bool, int> inspector, CancellationToken cancellationToken)
136 {
137 var errors = this.InspectSubDirectories(inspector, Path.GetFullPath("."), cancellationToken);
138
139 foreach (var searchPattern in this.SearchPatterns)
140 {
141 if (!this.SearchPatternResults.Contains(searchPattern))
142 {
143 this.Messaging.Write(ErrorMessages.FileNotFound(null, searchPattern));
144 errors++;
145 }
146 }
147
148 return errors;
149 }
150
151 /// <summary>
152 /// Inspect sub-directories.
153 /// </summary>
154 /// <param name="inspector"></param>
155 /// <param name="directory">The directory whose sub-directories will be inspected.</param>
156 /// <param name="cancellationToken"></param>
157 /// <returns>The number of errors that were found.</returns>
158 private int InspectSubDirectories(Func<string, bool, int> inspector, string directory, CancellationToken cancellationToken)
159 {
160 var errors = 0;
161
162 foreach (var searchPattern in this.SearchPatterns)
163 {
164 foreach (var sourceFilePath in GetFiles(directory, searchPattern))
165 {
166 cancellationToken.ThrowIfCancellationRequested();
167
168 var file = new FileInfo(sourceFilePath);
169
170 if (!this.ExemptFiles.Contains(file.Name.ToUpperInvariant()))
171 {
172 this.SearchPatternResults.Add(searchPattern);
173 errors += inspector(file.FullName, !this.DryRun);
174 }
175 }
176 }
177
178 if (this.Recurse)
179 {
180 foreach (var childDirectoryPath in Directory.GetDirectories(directory))
181 {
182 errors += this.InspectSubDirectories(inspector, childDirectoryPath, cancellationToken);
183 }
184 }
185
186 return errors;
187 }
188
189 /// <summary>
190 /// Parse the primary and secondary settings files.
191 /// </summary>
192 /// <param name="localSettingsFile1">The primary settings file.</param>
193 /// <param name="localSettingsFile2">The secondary settings file.</param>
194 private void ParseSettingsFiles(string localSettingsFile1, string localSettingsFile2)
195 {
196 if (null == localSettingsFile1 && null != localSettingsFile2)
197 {
198 throw new ArgumentException("Cannot specify a secondary settings file (set2) without a primary settings file (set1).", nameof(localSettingsFile2));
199 }
200
201 var settingsFile = localSettingsFile1;
202 while (null != settingsFile)
203 {
204 var doc = new XmlDocument();
205 doc.Load(settingsFile);
206
207 // get the types of tests that will have their errors displayed as warnings
208 var testsIgnoredElements = doc.SelectNodes("/Settings/IgnoreErrors/Test");
209 foreach (XmlElement test in testsIgnoredElements)
210 {
211 var key = test.GetAttribute("Id");
212 this.IgnoreErrors.Add(key);
213 }
214
215 // get the types of tests that will have their errors displayed as warnings
216 var testsAsWarningsElements = doc.SelectNodes("/Settings/ErrorsAsWarnings/Test");
217 foreach (XmlElement test in testsAsWarningsElements)
218 {
219 var key = test.GetAttribute("Id");
220 this.ErrorsAsWarnings.Add(key);
221 }
222
223 // get the exempt files
224 var localExemptFiles = doc.SelectNodes("/Settings/ExemptFiles/File");
225 foreach (XmlElement file in localExemptFiles)
226 {
227 var key = file.GetAttribute("Name").ToUpperInvariant();
228 this.ExemptFiles.Add(key);
229 }
230
231 settingsFile = localSettingsFile2;
232 localSettingsFile2 = null;
233 }
234 }
235
236 /// <summary>
237 /// Get the files that match a search path pattern.
238 /// </summary>
239 /// <param name="baseDir">The base directory at which to begin the search.</param>
240 /// <param name="searchPath">The search path pattern.</param>
241 /// <returns>The files matching the pattern.</returns>
242 private static string[] GetFiles(string baseDir, string searchPath)
243 {
244 // convert alternate directory separators to the standard one
245 var filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
246 var lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
247 string[] files = null;
248
249 try
250 {
251 if (0 > lastSeparator)
252 {
253 files = Directory.GetFiles(baseDir, filePath);
254 }
255 else // found directory separator
256 {
257 var searchPattern = filePath.Substring(lastSeparator + 1);
258
259 files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), searchPattern);
260 }
261 }
262 catch (DirectoryNotFoundException)
263 {
264 // don't let this function throw the DirectoryNotFoundException. (this exception
265 // occurs for non-existant directories and invalid characters in the searchPattern)
266 }
267
268 return files;
269 }
270 }
271 }