File size: 8,062 Bytes
1ae2e8e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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
        {
            [Option('v', "version", Required = false, HelpText = "Return the compiler version.")]
            public bool Version { get; set; }

            [Option('w', "watch", Required = false, HelpText = "Watches the directory and re-compiles when changes are detected.")]
            public bool WatchMode { get; set; }

            [Option("init", Required = false, HelpText = "Initialize a new roblox-cs project.")]
            public bool Init { get; set; }

            [Value(0, Required = false, HelpText = "Project directory path", MetaName = "Path")]
            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);
            }
        }
    }
}