Spaces:
Sleeping
Sleeping
| using System.Reflection; | |
| using LibGit2Sharp; | |
| using CommandLine; | |
| using Spectre.Console; | |
| using DotNet = Microsoft.DotNet.Cli.Utils; | |
| namespace RobloxCS.CLI | |
| { | |
| public static class Program | |
| { | |
| private const string _exampleProjectRepo = "https://github.com/roblox-csharp/example-project.git"; | |
| private static readonly Assembly _compilerAssembly = Assembly.Load("RobloxCS"); | |
| private static readonly System.Version _version = _compilerAssembly.GetName().Version!; | |
| public class Options | |
| { | |
| [] | |
| public bool Version { get; set; } | |
| [] | |
| public bool WatchMode { get; set; } | |
| [] | |
| public bool Init { get; set; } | |
| [] | |
| public string? Path { get; set; } | |
| } | |
| public static void Main(string[] args) | |
| { | |
| Parser.Default.ParseArguments<Options>(args) | |
| .WithParsed(options => | |
| { | |
| var path = options.Path ?? "."; | |
| if (options.Version) | |
| { | |
| Console.WriteLine($"roblox-cs {_version.Major}.{_version.Minor}.{_version.Build}"); | |
| } | |
| else if (options.Init) | |
| { | |
| InitializeProject(path); | |
| } | |
| else if (options.WatchMode) | |
| { | |
| StartWatchMode(path); | |
| } | |
| else | |
| { | |
| new Transpiler(path).Transpile(); | |
| } | |
| }); | |
| } | |
| private static void StartWatchMode(string path) | |
| { | |
| var transpiler = new Transpiler(path); | |
| var watcher = new FileSystemWatcher | |
| { | |
| Path = path, | |
| NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.Size, | |
| Filter = "*.*", | |
| IncludeSubdirectories = true | |
| }; | |
| watcher.Changed += (sender, e) => OnFileChanged(path, e); | |
| watcher.Created += (sender, e) => OnFileChanged(path, e); | |
| watcher.Renamed += (sender, e) => OnFileChanged(path, e); | |
| watcher.Deleted += (sender, e) => OnFileChanged(path, e); | |
| watcher.EnableRaisingEvents = true; | |
| AnsiConsole.MarkupLine("[yellow]Watching for file changes. Press \"enter\" to exit.[/]"); | |
| Console.WriteLine(); | |
| Console.ReadLine(); | |
| } | |
| private static void OnFileChanged(string path, FileSystemEventArgs e) | |
| { | |
| HashSet<WatcherChangeTypes> validChangeTypes = [WatcherChangeTypes.Changed, WatcherChangeTypes.Created, WatcherChangeTypes.Renamed]; | |
| if (!validChangeTypes.Contains(e.ChangeType)) return; | |
| var filePath = Utility.FixPathSep(e.FullPath); | |
| var extension = Path.GetExtension(filePath); | |
| var transpiler = new Transpiler(path); | |
| var isValidLuaFile = extension == ".lua" | |
| && !filePath.EndsWith("include/RuntimeLib.lua") | |
| && !filePath.Contains($"/{transpiler.Config.OutputFolder}/"); | |
| if (extension == ".cs" || isValidLuaFile || extension == ".yml") | |
| { | |
| AnsiConsole.MarkupLine($"[yellow]{(e.ChangeType == WatcherChangeTypes.Renamed ? "Changed" : e.ChangeType)}: {filePath}[/]"); | |
| transpiler.Transpile(); | |
| } | |
| } | |
| private static void InitializeProject(string path) | |
| { | |
| var projectName = Path.GetDirectoryName(path) == "" ? Path.GetFileName(path) : Path.GetDirectoryName(path) ?? "Example"; | |
| try | |
| { | |
| Repository.Clone(_exampleProjectRepo, path); | |
| AnsiConsole.MarkupLine($"[cyan]Repository cloned to {Path.GetFullPath(path)}[/]"); | |
| } | |
| catch (Exception ex) | |
| { | |
| AnsiConsole.MarkupLine($"[red]Failed to clone example project repository: {ex.Message}[/]"); | |
| Environment.Exit(1); | |
| } | |
| DeleteDirectoryManual(Path.Combine(path, ".git")); | |
| DotNet.Command.Create("dotnet", ["restore", path]).Execute(); | |
| AnsiConsole.MarkupLine("[cyan]Successfully restored .NET project.[/]"); | |
| if (projectName != "Example") | |
| { | |
| var projectFolder = path; | |
| var projectSourceFolder = Path.Combine(projectFolder, projectName, "src"); | |
| var projectFile = Path.Combine(projectSourceFolder, $"{projectName}.csproj"); | |
| var rojoManifestFile = Path.Combine(projectFolder, projectName, "default.project.json"); | |
| AnsiConsole.MarkupLine("[yellow]Renaming project...[/]"); | |
| File.Delete(Path.Combine(path, "Example.sln")); | |
| DotNet.Command.Create("dotnet", ["new", "sln", "-n", projectName, "-o", path]).Execute(); | |
| Directory.Move(Path.Combine(path, "Example"), Path.Combine(projectFolder, projectName)); | |
| File.Move(Path.Combine(projectSourceFolder, "Example.csproj"), projectFile); | |
| // Dynamicallty replace <Title>...</Title> inside of the csproj file. This sucks, and we must edit the solution to not lose the reference to it. | |
| var lines = File.ReadAllText(projectFile); | |
| var start = lines.IndexOf("<Title>", StringComparison.InvariantCulture) + "<Title>".Length; | |
| var end = lines.IndexOf("</Title>", StringComparison.InvariantCulture); | |
| lines = lines.Remove(start, end - start); | |
| lines = lines.Insert(start, projectName); | |
| File.WriteAllText(projectFile, lines); | |
| DotNet.Command.Create("dotnet", ["sln", Path.Combine(path, $"{projectName}.sln"), "add", projectFile]).Execute(); | |
| AnsiConsole.MarkupLine("[cyan]Successfully renamed project.[/]"); | |
| AnsiConsole.MarkupLine("[yellow]Modifying rojo manifest...[/]"); | |
| var rojoManifest = File.ReadAllText(rojoManifestFile); | |
| // Match for the name JSON key and its end and replace the name of it, horrible, but works. | |
| var idxOfStart = rojoManifest.IndexOf("\"name\": \"", StringComparison.InvariantCulture) + "\"name\": \"".Length; | |
| var idxOfEnd = rojoManifest.IndexOf("\",", idxOfStart, StringComparison.InvariantCulture); | |
| rojoManifest = rojoManifest.Remove(idxOfStart, idxOfEnd - idxOfStart); | |
| rojoManifest = rojoManifest.Insert(idxOfStart, projectName); | |
| File.WriteAllText(rojoManifestFile, rojoManifest); | |
| } | |
| Console.WriteLine($"Configuration:"); | |
| var initRepo = AnsiConsole.Confirm("\t[yellow]Do you want to initialize a Git repository?[/]", true); | |
| if (initRepo) | |
| { | |
| Repository.Init(path); | |
| AnsiConsole.MarkupLine("[cyan]Successfully initialized Git repository.[/]"); | |
| } | |
| } | |
| private static void DeleteDirectoryManual(string path) | |
| { | |
| if (Directory.Exists(path)) | |
| { | |
| foreach (string file in Directory.GetFiles(path)) | |
| { | |
| File.SetAttributes(file, FileAttributes.Normal); // Ensure file is not read-only | |
| File.Delete(file); | |
| } | |
| foreach (string subdir in Directory.GetDirectories(path)) | |
| { | |
| DeleteDirectoryManual(subdir); | |
| } | |
| Directory.Delete(path, recursive: true); | |
| } | |
| } | |
| } | |
| } |