-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMisc.cs
425 lines (407 loc) · 17 KB
/
Misc.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
using Newtonsoft.Json;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
namespace CobbleBuild {
/// <summary>
/// Async runs the actions asyncronously, Sync runs the actions syncronously, and SyncOnDemand runs the actions when AddOrRun() is called.
/// </summary>
enum ActionGroupType {
Sync, Async, SyncOnDemand
}
/// <summary>
/// Internal class designed to be able to either run the actions syncronously or asyncronously.
/// Deprecated: Not really ideal. Didn't meaningfully improve performance.
/// </summary>
[Obsolete]
internal class ActionGroup {
public ActionGroupType type;
private List<Action>? _actions;
public ActionGroup(ActionGroupType type) {
this.type = type;
if (type != ActionGroupType.SyncOnDemand) {
_actions = new List<Action>();
}
}
/// <summary>
/// Adds the tasks to be run unless set to on demand.
/// </summary>
/// <param name="action"></param>
public void AddOrRun(Action action) {
if (type != ActionGroupType.SyncOnDemand) {
_actions.Add(action);
}
else {
action();
}
}
private void runAllSync() {
foreach (var item in _actions) {
item();
}
}
private void runAllAsync() {
var tasks = new Task[_actions.Count];
for (var i = 0; i < tasks.Length; i++) {
tasks[i] = Task.Factory.StartNew(_actions[i]);
}
Task.WaitAll(tasks);
}
/// <summary>
/// Executes all tasks and measures how long it takes. Returns 0 if mode is set to SyncOnDemand
/// </summary>
/// <returns>Time taken in ms</returns>
public long ExecuteAll() {
if (type == ActionGroupType.SyncOnDemand) {
return 0;
}
var timer = Stopwatch.StartNew();
//(type == ActionGroupType.Sync) ? runAllSync() : runAllAsync();
if (type == ActionGroupType.Sync) {
runAllSync();
}
else {
runAllAsync();
}
timer.Stop();
return timer.ElapsedMilliseconds;
}
}
internal static class Misc {
public static List<string> validRegionalVariants { get; set; } = ["alolan", "galarian", "valencian"];
private static string generatedDisclaimer = @"/* This File was generated and/or processed with CobbleBuild.
* These files might be overwritable in the future, but for now please submit any issues to CobbleBuild
* instead of fixing them here. This file should not be overwritten in cobblemon-bedrock.
*/
";
/// <summary>
/// Serializes the passed in object as a json and saves it to the passed in path.
/// </summary>
/// <param name="anyObject">Object to be serialized.</param>
/// <param name="path">Path to save the file to/</param>
/// <param name="generatedHeading">Adds the comment at the top of the file saying that it is auto-generated. Defaults to true.</param>
public static void SaveToJson(object anyObject, string path, bool generatedHeading = true) {
string jsonData = JsonConvert.SerializeObject(anyObject, Config.config.SerializerSettings);
if (generatedHeading)
jsonData = jsonData.Insert(0, generatedDisclaimer);
File.WriteAllText(path, jsonData);
}
public static async Task SaveToJsonAsync(object anyObject, string path, bool generatedHeading = true) {
string jsonData = JsonConvert.SerializeObject(anyObject, Config.config.SerializerSettings);
if (generatedHeading)
jsonData = jsonData.Insert(0, generatedDisclaimer);
await File.WriteAllTextAsync(path, jsonData);
}
/// <summary>
/// Takes in a filepath and deserializes it into the specified type
/// </summary>
/// <typeparam name="T">Type to be serialized into.</typeparam>
/// <param name="path">Path to load the json from.</param>
/// <returns>Deserialized object</returns>
/// <exception cref="Exception">Generic deserialization failed error.</exception>
/// <exception cref="FileNotFoundException">File was not found.</exception>
public static T LoadFromJson<T>(string path) {
if (!File.Exists(path))
throw new FileNotFoundException($"File {Path.GetFileName(path)} was not found.");
T? data = JsonConvert.DeserializeObject<T>(File.ReadAllText(path));
if (data == null)
throw new Exception("The JSON File could not be read.");
return data;
}
public static async Task<T> LoadFromJsonAsync<T>(string path) {
if (!File.Exists(path))
throw new FileNotFoundException($"File {Path.GetFileName(path)} was not found.");
T? data = JsonConvert.DeserializeObject<T>(await File.ReadAllTextAsync(path));
if (data == null)
throw new Exception("The JSON File could not be read.");
return data;
}
/// <summary>
/// Somehow, this is not an inbuilt function.
/// </summary>
/// <param name="sourcePath">Source File Path</param>
/// <param name="outputPath">Destination File Path</param>
public static async Task CopyAsync(string sourcePath, string outputPath) {
using (var sourceStream = File.OpenRead(sourcePath)) {
using (var destStream = File.Create(outputPath)) {
await sourceStream.CopyToAsync(destStream);
}
}
}
/// <summary>
/// Prints the message to console and terminates the process.
/// </summary>
public static void error(string msg) {
Debug.WriteLine(msg);
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(msg);
Console.ForegroundColor = ConsoleColor.White;
Environment.Exit(1);
}
/// <summary>
/// Prints the message to the consol in red, but does not terminate the process.
/// </summary>
/// <param name="msg"></param>
public static void softError(string msg) {
Debug.WriteLine(msg);
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(msg);
Console.ForegroundColor = ConsoleColor.White;
}
/// <summary>
/// Prints message to console in yellow.
/// </summary>
public static void warn(string msg) {
Debug.WriteLine(msg);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(msg);
Console.ForegroundColor = ConsoleColor.White;
}
public static void info(string msg) {
Debug.WriteLine(msg);
Console.WriteLine(msg);
}
public static void printInnerExceptions(AggregateException exception) {
foreach (var item in exception.InnerExceptions) {
softError(item.Message);
}
}
/// <summary>
/// Extracts a file to a directory. Adds file to temporaryFolders list if config.temporairlyExtract is true.
/// </summary>
/// <param name="filePath">Path to the file to be extracted.</param>
/// <param name="outputPath">Optional output path. If null, it will extract to the extractedResources folder</param>
/// <returns>The path that the files were extracted to.</returns>
/// <exception cref="FileNotFoundException"> Thrown when file specified in filePath doesnt exist.</exception>
public static string ExtractZipFile(string filePath, string? outputPath = null) {
if (!File.Exists(filePath))
throw new FileNotFoundException($"File {filePath} could not be found.");
if (outputPath == null)
outputPath = Path.Combine(Config.config.projectPath, "extractedResources", Path.GetFileNameWithoutExtension(filePath));
Directory.CreateDirectory(outputPath);
System.IO.Compression.ZipFile.ExtractToDirectory(filePath, outputPath, true);
if (Config.config.temporairlyExtract)
Program.temporaryFolders.Add(outputPath);
return outputPath;
}
public static string ToTitleCase(string input) {
TextInfo textInfo = CultureInfo.CurrentCulture.TextInfo;
return textInfo.ToTitleCase(input.Replace("_", " "));
}
//Basic Yes or no prompt
public static bool yesOrNo(string question) {
Console.Write(question);
Console.Write("(y/n):");
ConsoleKeyInfo key = Console.ReadKey();
Console.WriteLine();
return (key.Key == ConsoleKey.Y);
}
public static T? DeserializeFromFile<T>(string filePath) {
return JsonConvert.DeserializeObject<T>(File.ReadAllText(filePath));
}
//Yoinked from stackoverflow
public static void OpenUri(string url) {
try {
Process.Start(url);
}
catch {
//I don't know that this is still necessary but stack overflow guy kept it in so no touch I don't have mac to test
// hack because of this: https://github.com/dotnet/corefx/issues/10361
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
url = url.Replace("&", "^&");
Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true });
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
Process.Start("xdg-open", url);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
Process.Start("open", url);
}
else {
throw;
}
}
}
public static void RunCmd(string FileName, string arguments, string workingDirectory, bool throwErrors = true) {
if (Config.config.usePowershell) {
Process process = new Process();
process.StartInfo = new ProcessStartInfo("powershell.exe", $"{FileName} {arguments}") {
WorkingDirectory = workingDirectory,
UseShellExecute = false,
//RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
};
process.Start();
process.WaitForExit();
if (process.ExitCode != 0) {
var message = "Deploy process exited with code " + process.ExitCode;
if (throwErrors)
throw new ApplicationException(message);
else
softError(message);
}
}
else {
Process process = new Process();
process.StartInfo = new ProcessStartInfo(FileName, arguments) {
WorkingDirectory = workingDirectory,
UseShellExecute = true
};
process.Start();
process.WaitForExit();
if (process.ExitCode != 0) {
var message = "Deploy process exited with code " + process.ExitCode;
if (throwErrors)
throw new ApplicationException(message);
else
softError(message);
}
}
}
/// <summary>
/// Get all files in a Dir and Subdirs
/// Old implementation
/// ExcludeDirs must contain exact path
/// </summary>
/// <param name="dirPath">Path of dir to look in.</param>
/// <param name="excludeDirs">Full Path of directories to exclude.</param>
/// <returns>Paths of Files</returns>
public static string[] getAllFilesInDirandSubDirs(string dirPath, params string[] excludeDirs) {
List<string> output = [];
if (!excludeDirs.Contains(dirPath))
output = output.Union(Directory.GetFiles(dirPath)).ToList();
for (int i = 0; i < excludeDirs.Length; i++) {
excludeDirs[i] = excludeDirs[i].Replace("\\", "/");
}
excludeDirs = excludeDirs.Select(x => x.Replace("\\", "/")).ToArray();
foreach (string dir in Directory.GetDirectories(dirPath)) {
if (!excludeDirs.Contains(dir.Replace("\\", "/"))) {
foreach (string subdir in getAllFilesInDirandSubDirs(dir, excludeDirs)) {
output.Add(subdir);
}
}
}
return output.ToArray();
}
/// <summary>
/// Get all files in dir and subdir
/// </summary>
/// <param name="dirPath">Path to directoyr</param>
/// <param name="excludeDirs">Regex to use against Dirs to exlclude them. Does not use params to prevent ambiguous calls.</param>
/// <returns>Array of Filepaths</returns>
public static string[] getAllFilesInDirandSubDirs(string dirPath, Regex[] excludeDirs) {
List<string> output = new List<string>();
if (!excludeDirs.Any(x => x.IsMatch(dirPath)))
output = output.Union(Directory.GetFiles(dirPath)).ToList();
foreach (string dir in Directory.GetDirectories(dirPath)) {
if (!excludeDirs.Any(x => x.IsMatch(dir))) {
foreach (string subdir in getAllFilesInDirandSubDirs(dir, excludeDirs)) {
output.Add(subdir);
}
}
}
return output.ToArray();
}
//Meh
public static string getInternalLocation(string outputPath) {
string output = getPathFrom(outputPath, "textures");
return output.Remove(output.Length - 4);
}
/// <summary>
/// Returns part of the path after the specified folder name.
/// ex: C:/Users/name/source/repos/test.exe with a foldername parameter of "source" would return source/repos/test.exe
/// </summary>
/// <param name="folderPath"></param>
/// <param name="folderNameFrom"></param>
/// <param name="includeFolderListed">Whether or not the specified folderName should remain in the output path</param>
/// <exception cref="Exception">The Folder was not found in the path.</exception>
public static string getPathFrom(string folderPath, string folderNameFrom, bool includeFolderListed = true) {
folderPath = folderPath.Replace("\\", "/"); //Normalizing input
int index = folderPath.IndexOf($"{folderNameFrom}/", StringComparison.OrdinalIgnoreCase);
if (index == -1) {
throw new Exception($"Folder Path {folderPath} does not contain folder {folderNameFrom}");
}
else {
return folderPath.Substring(index + (includeFolderListed ? 0 : folderNameFrom.Length + 1));
}
}
public static bool tryRemoveNamespace(string item, out string newItem) {
Match nameMatch = Regex.Match(item, @":(.+)");
if (!nameMatch.Success) {
newItem = item;
Misc.warn($"Couldn't remove namespace from {item}");
return false;
}
newItem = nameMatch.Groups[1].Value;
return true;
}
/// <summary>
/// Excludes all characters that arent letters, digits.
/// </summary>
/// <param name="item"></param>
/// <returns>Standardized id</returns>
public static string toID(string item) {
return new string(item
.ToLower()
//.Replace(' ', '_')
.Where(c => char.IsLetterOrDigit(c)).ToArray());
}
/// <summary>
/// Takes in a string and adds a namespace to it, as well as removing all invalid characters.
/// </summary>
/// <param name="item"></param>
/// <param name="namespace">Namespace of the identifier </param>
/// <returns>Identifier "namespace:normalized_item_id"</returns>
public static string toIdentifier(string item, string @namespace = "cobblemon") {
return $"{@namespace}:{toID(item)}";
}
}
public class Registry<T> : Dictionary<string, List<T>> {
public void Register(string key, T value) {
if (this.ContainsKey(key)) {
this[key].Add(value);
}
else {
this[key] = [value];
}
}
public void Unregister(string key, T value) {
if (!this.ContainsKey(key))
return;
foreach (var item in this[key]) {
if (object.Equals(item, value))
this[key].Remove(value);
}
}
public void UnregisterAll(string key) {
if (this.ContainsKey(key))
this.Remove(key);
}
public T GetFirst(string key) {
if (!this.ContainsKey(key) || this[key].Count < 1)
throw new Exception("Key has nothing registered to it.");
return this[key][0];
}
public bool TryGetFirst(string key, out T? value) {
try {
value = GetFirst(key);
return true;
}
catch { }
value = default;
return false;
}
/// <summary>
/// Returns the amount of entries for a specific key.
/// </summary>
/// <param name="key"></param>
/// <returns>Amount or 0 if key not registered.</returns>
public int Amount(string key) {
if (!this.ContainsKey(key))
return 0;
return this[key].Count;
}
}
}