diff --git a/luacs-docs/lua/baseluadocs/Character.lua b/luacs-docs/lua/baseluadocs/Character.lua index f06a85fd6..b3ce44987 100644 --- a/luacs-docs/lua/baseluadocs/Character.lua +++ b/luacs-docs/lua/baseluadocs/Character.lua @@ -10,33 +10,8 @@ Barotrauma source code: [Character.cs](https://github.com/evilfactory/LuaCsForBa local Character = {} --- @remove function Character.Create(characterInfo, position, seed, id, isRemotePlayer, hasAi, ragdoll) end --- @remove function TeleportTo(worldPos) end -- @remove Character.CharacterList ---- Creates a Character using CharacterInfo. --- @realm server --- @tparam CharacterInfo characterInfo --- @tparam Vector2 position --- @tparam string seed --- @tparam number id --- @tparam bool isRemotePlayer --- @tparam bool hasAi --- @tparam RagdollParams ragdoll --- @treturn Character --- @usage --- local vsauce = CharacterInfo("human", "custom name") --- local character = Character.Create(vsauce, Vector2(0, 0), "some random characters") --- print(character) -function Character.Create(characterInfo, position, seed, id, isRemotePlayer, hasAi, ragdoll) end - - ---- Teleports a character to a position. --- @realm server --- @tparam Vector2 worldPos --- @usage --- Character.CharacterList[1].TeleportTo(Vector2(0, 0)) -- teleports first created characters to 0, 0 -function TeleportTo(worldPos) end --- -- Character.CharacterList, Table containing all characters. diff --git a/luacs-docs/lua/scripts/LuaDocsGenerator/DocsGenerator.cs b/luacs-docs/lua/scripts/LuaDocsGenerator/DocsGenerator.cs new file mode 100644 index 000000000..3f7993988 --- /dev/null +++ b/luacs-docs/lua/scripts/LuaDocsGenerator/DocsGenerator.cs @@ -0,0 +1,597 @@ +using FarseerPhysics.Collision.Shapes; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Metrics; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace LuaDocsGenerator +{ + public class DocsGenerator + { + private static HashSet removed = new HashSet(); + + public static string NormalizeGenericTypeName(string s) + { + var idx = s.LastIndexOf('`'); + + if (idx != -1) + { + return s[..idx]; + } + + return s; + } + + public static string TypeToString(Type type, bool useLuaTypes = true) + { + // Return "T" for unresolved type params + if (type.IsGenericParameter) + { + return type.Name; + } + + var genericType = type.IsGenericType + ? type.GetGenericTypeDefinition() + : null; + + if (type == typeof(bool)) + { + return "bool"; + } + + if (type == typeof(string)) + { + return "string"; + } + + if (useLuaTypes) + { + if (type == typeof(sbyte) + || type == typeof(byte) + || type == typeof(short) + || type == typeof(ushort) + || type == typeof(int) + || type == typeof(uint) + || type == typeof(long) + || type == typeof(ulong) + || type == typeof(float) + || type == typeof(double)) + { + return "number"; + } + + if (genericType == typeof(List<>) + || genericType == typeof(Dictionary<,>)) + { + return "table"; + } + + if (genericType == typeof(Action<,>) + || genericType == typeof(Func<,>)) + { + return "function"; + } + } + + var nsToRemove = new[] { + "Barotrauma", + "System", + "System.Collections", + "System.Collections.Generic", + }; + + string Namespaced(string typeName) + { + if (type.Namespace == null) + { + return typeName; + } + + // Full namespace match + if (nsToRemove.Contains(type.Namespace)) + { + return typeName; + } + + // Partial namespace match + foreach (var ns in nsToRemove) + { + if (ns == type.Namespace) + { + return typeName; + } + + if (type.Namespace.StartsWith(ns + ".")) + { + var shortNs = type.Namespace.Remove(0, ns.Length + 1); + return $"{shortNs}.{typeName}"; + } + } + + return $"{type.Namespace}.{typeName}"; + } + + string Impl(string? ns) + { + if (type.IsGenericType) + { + var genericTypeDef = type.GetGenericTypeDefinition(); + var genericTypeName = NormalizeGenericTypeName(genericTypeDef.Name); + + var genericArgs = type.GetGenericArguments(); + + // Use the `T?` notation instead of Nullable + if (genericTypeDef == typeof(Nullable<>)) + { + // ldoc supports the "?string" notation, which expands to "?|nil|string" + if (useLuaTypes) + { + return Namespaced("?" + TypeToString(genericArgs[0], useLuaTypes: false)); + } + else + { + return Namespaced(TypeToString(genericArgs[0], useLuaTypes: false) + "?"); + } + } + + var sb = new StringBuilder(); + sb.Append(genericTypeName); + sb.Append("<"); + foreach (var genericArgType in genericArgs) + { + sb.Append(TypeToString(genericArgType, useLuaTypes: false)); + sb.Append(","); + } + // Remove the last separator + sb.Length--; + sb.Append(">"); + + return Namespaced(sb.ToString()); + } + + return Namespaced(type.Name); + } + + return Impl(type.Namespace); + } + + public static (bool Success, string Output, string Error) TryRunGitCommand(string args) + { + static string? GetGitBinary() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Process) + ?.Split(';') + .Select(x => Path.Join(x, "git.exe")) + .FirstOrDefault(File.Exists); + } + else + { + return Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Process) + ?.Split(':') + .Select(x => Path.Join(x, "git")) + .FirstOrDefault(File.Exists); + } + } + + var gitBinary = GetGitBinary(); + if (gitBinary == null) + { + throw new InvalidOperationException("Failed to find git binary in PATH"); + } + + using var process = Process.Start(new ProcessStartInfo(gitBinary, args) + { + WindowStyle = ProcessWindowStyle.Hidden, + CreateNoWindow = true, + UseShellExecute = false, + RedirectStandardInput = true, + RedirectStandardError = true, + RedirectStandardOutput = true, + }); + + if (process == null) + throw new InvalidOperationException($"Failed to run git command: {args}"); + + process.Start(); + + var stdOut = process.StandardOutput.ReadToEndAsync(); + var stdErr = process.StandardError.ReadToEndAsync(); + Task.WhenAll(stdOut, stdErr).GetAwaiter().GetResult(); + process.WaitForExit(); + + return (process.ExitCode == 0, stdOut.Result.TrimEnd('\r', '\n'), stdErr.Result); + } + + private static string EscapeName(string n) + { + return n switch + { + "end" => "endparam", + _ => n + }; + } + + private static string? ConvertAnnotation(Type type, MemberInfo member, string realm) + { + switch (member.MemberType) + { + case MemberTypes.Method: + return ConvertAnnotation(type, (MethodInfo)member, realm); + case MemberTypes.Field: + return ConvertAnnotation(type, (FieldInfo)member, realm); + case MemberTypes.Property: + return ConvertAnnotation(type, (PropertyInfo)member, realm); + } + + return null; + } + + private static string? ConvertAnnotation(Type type, MethodInfo method, string realm) + { + StringBuilder sb = new StringBuilder(); + + // Exclude property getters/setters + if (method.IsSpecialName) + { + return null; + } + + var paramNames = new StringBuilder(); + foreach (var parameter in method.GetParameters()) + { + paramNames.Append(EscapeName(parameter.Name!)); + paramNames.Append(", "); + } + if (paramNames.Length > 0) + { + // Remove the last separator + paramNames.Length -= 2; + } + + string functionDecoration; + if (method.IsStatic) + { + functionDecoration = $"function {type.Name}.{method.Name}({paramNames}) end"; + } + else + { + functionDecoration = $"function {method.Name}({paramNames}) end"; + } + + if (removed.Contains("-- @remove " + functionDecoration)) + { + Console.WriteLine($"removed {functionDecoration}"); + return null; + } + + Console.WriteLine($" - METHOD: {method}"); + + sb.AppendLine($"--- {method.Name}"); + sb.AppendLine($"-- @realm {realm}"); + + foreach (var parameter in method.GetParameters()) + { + sb.AppendLine($"-- @tparam {TypeToString(parameter.ParameterType)} {EscapeName(parameter.Name!)}"); + } + + if (method.ReturnType != typeof(void)) + { + sb.AppendLine($"-- @treturn {TypeToString(method.ReturnType)}"); + } + + sb.AppendLine(functionDecoration); + + return sb.ToString(); + } + + private static string? ConvertAnnotation(Type type, FieldInfo field, string realm) + { + StringBuilder sb = new StringBuilder(); + + var name = EscapeName(field.Name); + var returnName = TypeToString(field.FieldType); + + if (field.IsStatic) + { + name = type.Name + "." + field.Name; + } + + if (removed.Contains("-- @remove " + name)) + { + Console.WriteLine($"removed {name}"); + return null; + } + + Console.WriteLine($" - FIELD: {name}"); + + sb.AppendLine("---"); + sb.Append("-- "); + sb.Append(name); + sb.AppendLine($", field of type {returnName}"); + sb.AppendLine($"-- @realm {realm}"); + sb.AppendLine($"-- @field {name}"); + + return sb.ToString(); + } + + public static string? ConvertAnnotation(Type type, PropertyInfo property, string realm) + { + StringBuilder sb = new StringBuilder(); + + var name = EscapeName(property.Name); + var returnName = TypeToString(property.PropertyType); + + if (property.GetGetMethod()?.IsStatic == true || property.GetSetMethod()?.IsStatic == true) + { + name = type.Name + "." + property.Name; + } + + if (removed.Contains("-- @remove " + name)) + { + Console.WriteLine($"removed {name}"); + return null; + } + + Console.WriteLine($" - PROPERTY: {name}"); + + sb.AppendLine("---"); + sb.Append("-- "); + sb.Append(name); + sb.AppendLine($", field of type {returnName}"); + sb.AppendLine("-- @realm shared"); + sb.AppendLine($"-- @field {name}"); + + return sb.ToString(); + } + + private static string? GetSignature(MemberInfo member) + { + switch (member.MemberType) + { + case MemberTypes.Method: + return GetSignature((MethodInfo)member); + case MemberTypes.Field: + return GetSignature((FieldInfo)member); + case MemberTypes.Property: + return GetSignature((PropertyInfo)member); + } + + return null; + } + + private static string GetSignature(FieldInfo field) + { + StringBuilder sb = new StringBuilder(); + + sb.Append(field.FieldType.Name); + sb.Append(" "); + sb.Append(field.Name); + + return sb.ToString(); + } + + private static string GetSignature(PropertyInfo property) + { + StringBuilder sb = new StringBuilder(); + + sb.Append(property.PropertyType.Name); + sb.Append(" "); + sb.Append(property.Name); + + return sb.ToString(); + } + + private static string GetSignature(MethodInfo method) + { + StringBuilder sb = new StringBuilder(); + bool firstParam = true; + + sb.Append(method.ReturnType.Name); + sb.Append(' '); + sb.Append(method.Name); + + // Add method generics + if (method.IsGenericMethod) + { + sb.Append("<"); + foreach (var g in method.GetGenericArguments()) + { + if (firstParam) + { + firstParam = false; + } + else + { + sb.Append(", "); + } + sb.Append(g.Name); + } + sb.Append(">"); + } + sb.Append("("); + firstParam = true; + foreach (var param in method.GetParameters()) + { + if (firstParam) + { + firstParam = false; + if (method.IsDefined(typeof(System.Runtime.CompilerServices.ExtensionAttribute), false)) + { + sb.Append("this "); + } + } + + sb.Append(", "); + + if (param.ParameterType.IsByRef) + { + sb.Append("ref "); + } + else if (param.IsOut) + { + sb.Append("out "); + } + + sb.Append(param.ParameterType.Name); + sb.Append(' '); + + sb.Append(param.Name); + } + sb.Append(")"); + return sb.ToString(); + } + + private static string GenerateBaseDoc(Type type, string categoryName, string baseFile) + { + string baseLuaText; + try + { + baseLuaText = File.ReadAllText(baseFile); + } + catch (FileNotFoundException) + { + baseLuaText = @$"-- luacheck: ignore 111 + +--[[-- +{type.FullName} +]] +-- @code {categoryName} +-- @pragma nostrip +local {type.Name} = {{}}".ReplaceLineEndings("\n"); + + File.WriteAllText(baseFile, baseLuaText); + } + + removed = new HashSet(); + var removeTagPattern = new Regex("^-- @remove (.*)$", RegexOptions.Multiline); + var matches = removeTagPattern.Matches(baseLuaText); + + foreach (var match in matches.Cast()) + { + removed.Add(match.Value); + } + + return baseLuaText; + } + + public static void GenerateDocs(Type type, string baseFile, string outFile, string? categoryName = null) + { + categoryName ??= type.Name; + var sb = new StringBuilder(); + + Console.WriteLine($"Generating docs for {type}"); + + string baseDoc = GenerateBaseDoc(type, categoryName, baseFile); + + sb.Append(baseDoc); + sb.AppendLine(); + sb.AppendLine(); + + var members = type.GetMembers(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); + foreach (var member in members) + { + switch (member.MemberType) + { + case MemberTypes.Method: + { + sb.Append(ConvertAnnotation(type, (MethodInfo)member, "shared")); + sb.AppendLine(); + break; + } + + case MemberTypes.Field: + { + sb.Append(ConvertAnnotation(type, (FieldInfo)member, "shared")); + sb.AppendLine(); + break; + } + + case MemberTypes.Property: + { + sb.Append(ConvertAnnotation(type, (PropertyInfo)member, "shared")); + sb.AppendLine(); + break; + } + } + } + + new FileInfo(outFile).Directory.Create(); + File.WriteAllText(outFile, sb.ToString()); + } + + public static void GenerateDocs(Type clientType, Type serverType, string baseFile, string outFile, string? categoryName = null) + { + categoryName ??= clientType.Name; + var sb = new StringBuilder(); + + Console.WriteLine($"Generating docs for {clientType} and {serverType}"); + + sb.Append(GenerateBaseDoc(clientType, categoryName, baseFile)); + sb.AppendLine(); + sb.AppendLine(); + + List<(string?, MemberInfo)> clientTypes = new List<(string?, MemberInfo)>(); + List<(string?, MemberInfo)> serverTypes = new List<(string?, MemberInfo)>(); + + var clientMembers = clientType.GetMembers(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); + var serverMembers = serverType.GetMembers(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); + + foreach (var member in clientMembers) + { + clientTypes.Add((GetSignature(member), member)); + } + + foreach (var member in serverMembers) + { + serverTypes.Add((GetSignature(member), member)); + } + + var ids = clientTypes.Select(x => x.Item1).Intersect(serverTypes.Select(x => x.Item1)); + List<(string?, MemberInfo)> sharedTypes = clientTypes.Where(x => ids.Contains(x.Item1)).ToList(); + + foreach (var type in sharedTypes) + { + string? result = ConvertAnnotation(clientType, type.Item2, "shared"); + if (result != null) + { + sb.Append(result); + sb.AppendLine(); + } + } + + foreach (var type in clientTypes) + { + string? result = ConvertAnnotation(clientType, type.Item2, "client"); + + if (result != null && !sharedTypes.Select(x => x.Item1).Contains(type.Item1)) + { + sb.Append(result); + sb.AppendLine(); + } + } + + foreach (var type in serverTypes) + { + string? result = ConvertAnnotation(clientType, type.Item2, "server"); + + if (result != null && !sharedTypes.Select(x => x.Item1).Contains(type.Item1)) + { + sb.Append(result); + sb.AppendLine(); + } + } + + new FileInfo(outFile).Directory.Create(); + File.WriteAllText(outFile, sb.ToString()); + } + } +} diff --git a/luacs-docs/lua/scripts/LuaDocsGenerator/LuaDocsGenerator.csproj b/luacs-docs/lua/scripts/LuaDocsGenerator/LuaDocsGenerator.csproj index 5b397e13c..3c2a56c6f 100644 --- a/luacs-docs/lua/scripts/LuaDocsGenerator/LuaDocsGenerator.csproj +++ b/luacs-docs/lua/scripts/LuaDocsGenerator/LuaDocsGenerator.csproj @@ -7,7 +7,13 @@ - + + + BarotraumaClient + + + BarotraumaServer + diff --git a/luacs-docs/lua/scripts/LuaDocsGenerator/LuaDocsGenerator.sln b/luacs-docs/lua/scripts/LuaDocsGenerator/LuaDocsGenerator.sln index 30c0eed55..263aa1602 100644 --- a/luacs-docs/lua/scripts/LuaDocsGenerator/LuaDocsGenerator.sln +++ b/luacs-docs/lua/scripts/LuaDocsGenerator/LuaDocsGenerator.sln @@ -1,34 +1,102 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30114.105 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33122.133 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuaDocsGenerator", "LuaDocsGenerator.csproj", "{F1961973-E69E-41A7-BE13-4F931890BC70}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinuxClient", "..\..\..\..\Barotrauma\BarotraumaClient\LinuxClient.csproj", "{426071C4-6668-4CF9-A44C-5FBEB72C469B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LinuxClient", "..\..\..\..\Barotrauma\BarotraumaClient\LinuxClient.csproj", "{426071C4-6668-4CF9-A44C-5FBEB72C469B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Farseer.NetStandard", "..\..\..\..\Libraries\Farseer Physics Engine 3.5\Farseer.NetStandard.csproj", "{F8F85D52-11B5-4177-914C-9AC75345D089}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Farseer.NetStandard", "..\..\..\..\Libraries\Farseer Physics Engine 3.5\Farseer.NetStandard.csproj", "{F8F85D52-11B5-4177-914C-9AC75345D089}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinuxServer", "..\..\..\..\Barotrauma\BarotraumaServer\LinuxServer.csproj", "{562EDFE0-7AEB-4F1E-BC26-148DF42F4186}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LuaDocsGenerator", "LuaDocsGenerator.csproj", "{6E36A474-29B7-4F0C-96CE-79F11BF6C0A4}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Unstable|Any CPU = Unstable|Any CPU + Unstable|x64 = Unstable|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {426071C4-6668-4CF9-A44C-5FBEB72C469B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {426071C4-6668-4CF9-A44C-5FBEB72C469B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {426071C4-6668-4CF9-A44C-5FBEB72C469B}.Debug|x64.ActiveCfg = Debug|x64 + {426071C4-6668-4CF9-A44C-5FBEB72C469B}.Debug|x64.Build.0 = Debug|x64 + {426071C4-6668-4CF9-A44C-5FBEB72C469B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {426071C4-6668-4CF9-A44C-5FBEB72C469B}.Release|Any CPU.Build.0 = Release|Any CPU + {426071C4-6668-4CF9-A44C-5FBEB72C469B}.Release|x64.ActiveCfg = Release|x64 + {426071C4-6668-4CF9-A44C-5FBEB72C469B}.Release|x64.Build.0 = Release|x64 + {426071C4-6668-4CF9-A44C-5FBEB72C469B}.Unstable|Any CPU.ActiveCfg = Unstable|Any CPU + {426071C4-6668-4CF9-A44C-5FBEB72C469B}.Unstable|Any CPU.Build.0 = Unstable|Any CPU + {426071C4-6668-4CF9-A44C-5FBEB72C469B}.Unstable|x64.ActiveCfg = Unstable|x64 + {426071C4-6668-4CF9-A44C-5FBEB72C469B}.Unstable|x64.Build.0 = Unstable|x64 + {F8F85D52-11B5-4177-914C-9AC75345D089}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8F85D52-11B5-4177-914C-9AC75345D089}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8F85D52-11B5-4177-914C-9AC75345D089}.Debug|x64.ActiveCfg = Debug|x64 + {F8F85D52-11B5-4177-914C-9AC75345D089}.Debug|x64.Build.0 = Debug|x64 + {F8F85D52-11B5-4177-914C-9AC75345D089}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8F85D52-11B5-4177-914C-9AC75345D089}.Release|Any CPU.Build.0 = Release|Any CPU + {F8F85D52-11B5-4177-914C-9AC75345D089}.Release|x64.ActiveCfg = Release|x64 + {F8F85D52-11B5-4177-914C-9AC75345D089}.Release|x64.Build.0 = Release|x64 + {F8F85D52-11B5-4177-914C-9AC75345D089}.Unstable|Any CPU.ActiveCfg = Release|Any CPU + {F8F85D52-11B5-4177-914C-9AC75345D089}.Unstable|Any CPU.Build.0 = Release|Any CPU + {F8F85D52-11B5-4177-914C-9AC75345D089}.Unstable|x64.ActiveCfg = Debug|x64 + {F8F85D52-11B5-4177-914C-9AC75345D089}.Unstable|x64.Build.0 = Debug|x64 + {562EDFE0-7AEB-4F1E-BC26-148DF42F4186}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {562EDFE0-7AEB-4F1E-BC26-148DF42F4186}.Debug|Any CPU.Build.0 = Debug|Any CPU + {562EDFE0-7AEB-4F1E-BC26-148DF42F4186}.Debug|x64.ActiveCfg = Debug|x64 + {562EDFE0-7AEB-4F1E-BC26-148DF42F4186}.Debug|x64.Build.0 = Debug|x64 + {562EDFE0-7AEB-4F1E-BC26-148DF42F4186}.Release|Any CPU.ActiveCfg = Release|Any CPU + {562EDFE0-7AEB-4F1E-BC26-148DF42F4186}.Release|Any CPU.Build.0 = Release|Any CPU + {562EDFE0-7AEB-4F1E-BC26-148DF42F4186}.Release|x64.ActiveCfg = Release|x64 + {562EDFE0-7AEB-4F1E-BC26-148DF42F4186}.Release|x64.Build.0 = Release|x64 + {562EDFE0-7AEB-4F1E-BC26-148DF42F4186}.Unstable|Any CPU.ActiveCfg = Unstable|Any CPU + {562EDFE0-7AEB-4F1E-BC26-148DF42F4186}.Unstable|Any CPU.Build.0 = Unstable|Any CPU + {562EDFE0-7AEB-4F1E-BC26-148DF42F4186}.Unstable|x64.ActiveCfg = Unstable|x64 + {562EDFE0-7AEB-4F1E-BC26-148DF42F4186}.Unstable|x64.Build.0 = Unstable|x64 + {FDB16AC2-7947-4DCC-A46F-E9D1B05C0A1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FDB16AC2-7947-4DCC-A46F-E9D1B05C0A1E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FDB16AC2-7947-4DCC-A46F-E9D1B05C0A1E}.Debug|x64.ActiveCfg = Debug|Any CPU + {FDB16AC2-7947-4DCC-A46F-E9D1B05C0A1E}.Debug|x64.Build.0 = Debug|Any CPU + {FDB16AC2-7947-4DCC-A46F-E9D1B05C0A1E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FDB16AC2-7947-4DCC-A46F-E9D1B05C0A1E}.Release|Any CPU.Build.0 = Release|Any CPU + {FDB16AC2-7947-4DCC-A46F-E9D1B05C0A1E}.Release|x64.ActiveCfg = Release|Any CPU + {FDB16AC2-7947-4DCC-A46F-E9D1B05C0A1E}.Release|x64.Build.0 = Release|Any CPU + {FDB16AC2-7947-4DCC-A46F-E9D1B05C0A1E}.Unstable|Any CPU.ActiveCfg = Debug|Any CPU + {FDB16AC2-7947-4DCC-A46F-E9D1B05C0A1E}.Unstable|Any CPU.Build.0 = Debug|Any CPU + {FDB16AC2-7947-4DCC-A46F-E9D1B05C0A1E}.Unstable|x64.ActiveCfg = Debug|Any CPU + {FDB16AC2-7947-4DCC-A46F-E9D1B05C0A1E}.Unstable|x64.Build.0 = Debug|Any CPU + {49C8C464-A381-4FDD-AB52-80BB9FE35DDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {49C8C464-A381-4FDD-AB52-80BB9FE35DDB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {49C8C464-A381-4FDD-AB52-80BB9FE35DDB}.Debug|x64.ActiveCfg = Debug|Any CPU + {49C8C464-A381-4FDD-AB52-80BB9FE35DDB}.Debug|x64.Build.0 = Debug|Any CPU + {49C8C464-A381-4FDD-AB52-80BB9FE35DDB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {49C8C464-A381-4FDD-AB52-80BB9FE35DDB}.Release|Any CPU.Build.0 = Release|Any CPU + {49C8C464-A381-4FDD-AB52-80BB9FE35DDB}.Release|x64.ActiveCfg = Release|Any CPU + {49C8C464-A381-4FDD-AB52-80BB9FE35DDB}.Release|x64.Build.0 = Release|Any CPU + {49C8C464-A381-4FDD-AB52-80BB9FE35DDB}.Unstable|Any CPU.ActiveCfg = Debug|Any CPU + {49C8C464-A381-4FDD-AB52-80BB9FE35DDB}.Unstable|Any CPU.Build.0 = Debug|Any CPU + {49C8C464-A381-4FDD-AB52-80BB9FE35DDB}.Unstable|x64.ActiveCfg = Debug|Any CPU + {49C8C464-A381-4FDD-AB52-80BB9FE35DDB}.Unstable|x64.Build.0 = Debug|Any CPU + {6E36A474-29B7-4F0C-96CE-79F11BF6C0A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E36A474-29B7-4F0C-96CE-79F11BF6C0A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E36A474-29B7-4F0C-96CE-79F11BF6C0A4}.Debug|x64.ActiveCfg = Debug|Any CPU + {6E36A474-29B7-4F0C-96CE-79F11BF6C0A4}.Debug|x64.Build.0 = Debug|Any CPU + {6E36A474-29B7-4F0C-96CE-79F11BF6C0A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E36A474-29B7-4F0C-96CE-79F11BF6C0A4}.Release|Any CPU.Build.0 = Release|Any CPU + {6E36A474-29B7-4F0C-96CE-79F11BF6C0A4}.Release|x64.ActiveCfg = Release|Any CPU + {6E36A474-29B7-4F0C-96CE-79F11BF6C0A4}.Release|x64.Build.0 = Release|Any CPU + {6E36A474-29B7-4F0C-96CE-79F11BF6C0A4}.Unstable|Any CPU.ActiveCfg = Debug|Any CPU + {6E36A474-29B7-4F0C-96CE-79F11BF6C0A4}.Unstable|Any CPU.Build.0 = Debug|Any CPU + {6E36A474-29B7-4F0C-96CE-79F11BF6C0A4}.Unstable|x64.ActiveCfg = Debug|Any CPU + {6E36A474-29B7-4F0C-96CE-79F11BF6C0A4}.Unstable|x64.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F1961973-E69E-41A7-BE13-4F931890BC70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F1961973-E69E-41A7-BE13-4F931890BC70}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F1961973-E69E-41A7-BE13-4F931890BC70}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F1961973-E69E-41A7-BE13-4F931890BC70}.Release|Any CPU.Build.0 = Release|Any CPU - {426071C4-6668-4CF9-A44C-5FBEB72C469B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {426071C4-6668-4CF9-A44C-5FBEB72C469B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {426071C4-6668-4CF9-A44C-5FBEB72C469B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {426071C4-6668-4CF9-A44C-5FBEB72C469B}.Release|Any CPU.Build.0 = Release|Any CPU - {F8F85D52-11B5-4177-914C-9AC75345D089}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F8F85D52-11B5-4177-914C-9AC75345D089}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F8F85D52-11B5-4177-914C-9AC75345D089}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F8F85D52-11B5-4177-914C-9AC75345D089}.Release|Any CPU.Build.0 = Release|Any CPU + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {681C79BD-27C3-4CAF-9622-A425B77CF1CD} EndGlobalSection EndGlobal diff --git a/luacs-docs/lua/scripts/LuaDocsGenerator/Program.cs b/luacs-docs/lua/scripts/LuaDocsGenerator/Program.cs index 8cd2ffe63..4a1bb92ce 100644 --- a/luacs-docs/lua/scripts/LuaDocsGenerator/Program.cs +++ b/luacs-docs/lua/scripts/LuaDocsGenerator/Program.cs @@ -1,437 +1,87 @@ -using System.Diagnostics; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Text; -using System.Text.RegularExpressions; -using Barotrauma; -using Barotrauma.Networking; +extern alias BarotraumaClient; +extern alias BarotraumaServer; -string NormalizeGenericTypeName(string s) +namespace LuaDocsGenerator { - var idx = s.LastIndexOf('`'); - if (idx != -1) + internal class Program { - return s[..idx]; - } + private static string basePath = ""; + private static string generatedDir = ""; + private static string baseLuaDir = ""; - return s; + private static void GenerateDocs(string file, string? categoryName = null) + { + DocsGenerator.GenerateDocs(typeof(T), $"{baseLuaDir}/{file}", $"{generatedDir}/{file}", categoryName); + } + + private static void GenerateDocs(string file, string? categoryName = null) + { + DocsGenerator.GenerateDocs(typeof(T1), typeof(T2), $"{baseLuaDir}/{file}", $"{generatedDir}/{file}", categoryName); + } + + private static void GenerateDocs(Type clientType, Type serverType, string file, string? categoryName = null) + { + DocsGenerator.GenerateDocs(clientType, serverType, $"{baseLuaDir}/{file}", $"{generatedDir}/{file}", categoryName); + } + + static void Main(string[] args) + { + var gitDir = (new Func(() => + { + var (success, gitDir, error) = DocsGenerator.TryRunGitCommand("rev-parse --show-toplevel"); + if (!success) + { + throw new InvalidDataException($"Failed to determine the root of the git repo: {error}"); + } + + return gitDir; + }))(); + + basePath = $"{gitDir}/luacs-docs/lua"; + generatedDir = $"{basePath}/lua/generated"; + baseLuaDir = $"{basePath}/baseluadocs"; + + try + { + Directory.Delete(generatedDir, true); + } + catch (DirectoryNotFoundException) { } + + Directory.CreateDirectory(generatedDir); + Directory.CreateDirectory(baseLuaDir); + + GenerateDocs("Character.lua"); + GenerateDocs("CharacterInfo.lua"); + GenerateDocs("CharacterHealth.lua"); + GenerateDocs("AnimController.lua"); + GenerateDocs("Client.lua"); + GenerateDocs("Entity.lua"); + GenerateDocs("Entity.Spawner.lua", "Entity.Spawner"); + GenerateDocs("Item.lua"); + GenerateDocs("ItemPrefab.lua"); + GenerateDocs("Submarine.lua"); + GenerateDocs("SubmarineInfo.lua"); + GenerateDocs("Job.lua"); + GenerateDocs("JobPrefab.lua"); + GenerateDocs("GameSession.lua"); + GenerateDocs("NetLobbyScreen.lua"); + GenerateDocs("GameScreen.lua"); + GenerateDocs("World.lua", "Game.World"); + GenerateDocs("Inventory.lua"); + GenerateDocs("ItemInventory.lua"); + GenerateDocs("CharacterInventory.lua"); + GenerateDocs("Hull.lua"); + GenerateDocs("Level.lua"); + GenerateDocs("Affliction.lua"); + GenerateDocs("AfflictionPrefab.lua"); + GenerateDocs("WayPoint.lua"); + GenerateDocs("ServerSettings.lua", "Game.ServerSettings"); + GenerateDocs(typeof(BarotraumaClient::Barotrauma.GameSettings), typeof(BarotraumaServer::Barotrauma.GameSettings), "GameSettings.lua", "Game.Settings"); + GenerateDocs("Structure.lua"); + GenerateDocs("StructurePrefab.lua"); + GenerateDocs("PhysicsBody.lua"); + GenerateDocs("Limb.lua"); + } + } } - -string TypeToString(Type type, bool useLuaTypes = true) -{ - // Return "T" for unresolved type params - if (type.IsGenericParameter) - { - return type.Name; - } - - var genericType = type.IsGenericType - ? type.GetGenericTypeDefinition() - : null; - - if (type == typeof(bool)) - { - return "bool"; - } - - if (type == typeof(string)) - { - return "string"; - } - - if (useLuaTypes) - { - if (type == typeof(sbyte) - || type == typeof(byte) - || type == typeof(short) - || type == typeof(ushort) - || type == typeof(int) - || type == typeof(uint) - || type == typeof(long) - || type == typeof(ulong) - || type == typeof(float) - || type == typeof(double)) - { - return "number"; - } - - if (genericType == typeof(List<>) - || genericType == typeof(Dictionary<,>)) - { - return "table"; - } - - if (genericType == typeof(Action<,>) - || genericType == typeof(Func<,>)) - { - return "function"; - } - } - - var nsToRemove = new[] { - "Barotrauma", - "System", - "System.Collections", - "System.Collections.Generic", - }; - - string Namespaced(string typeName) - { - if (type.Namespace == null) - { - return typeName; - } - - // Full namespace match - if (nsToRemove.Contains(type.Namespace)) - { - return typeName; - } - - // Partial namespace match - foreach (var ns in nsToRemove) - { - if (ns == type.Namespace) - { - return typeName; - } - - if (type.Namespace.StartsWith(ns + ".")) - { - var shortNs = type.Namespace.Remove(0, ns.Length + 1); - return $"{shortNs}.{typeName}"; - } - } - - return $"{type.Namespace}.{typeName}"; - } - - string Impl(string? ns) - { - if (type.IsGenericType) - { - var genericTypeDef = type.GetGenericTypeDefinition(); - var genericTypeName = NormalizeGenericTypeName(genericTypeDef.Name); - - var genericArgs = type.GetGenericArguments(); - - // Use the `T?` notation instead of Nullable - if (genericTypeDef == typeof(Nullable<>)) - { - // ldoc supports the "?string" notation, which expands to "?|nil|string" - if (useLuaTypes) - { - return Namespaced("?" + TypeToString(genericArgs[0], useLuaTypes: false)); - } - else - { - return Namespaced(TypeToString(genericArgs[0], useLuaTypes: false) + "?"); - } - } - - var sb = new StringBuilder(); - sb.Append(genericTypeName); - sb.Append("<"); - foreach (var genericArgType in genericArgs) - { - sb.Append(TypeToString(genericArgType, useLuaTypes: false)); - sb.Append(","); - } - // Remove the last separator - sb.Length--; - sb.Append(">"); - - return Namespaced(sb.ToString()); - } - - return Namespaced(type.Name); - } - - return Impl(type.Namespace); -} - -static (bool Success, string Output, string Error) TryRunGitCommand(string args) -{ - static string? GetGitBinary() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Process) - ?.Split(';') - .Select(x => Path.Join(x, "git.exe")) - .FirstOrDefault(File.Exists); - } - else - { - return Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Process) - ?.Split(':') - .Select(x => Path.Join(x, "git")) - .FirstOrDefault(File.Exists); - } - } - - var gitBinary = GetGitBinary(); - if (gitBinary == null) - { - throw new InvalidOperationException("Failed to find git binary in PATH"); - } - - using var process = Process.Start(new ProcessStartInfo(gitBinary, args) - { - WindowStyle = ProcessWindowStyle.Hidden, - CreateNoWindow = true, - UseShellExecute = false, - RedirectStandardInput = true, - RedirectStandardError = true, - RedirectStandardOutput = true, - }); - - if (process == null) - throw new InvalidOperationException($"Failed to run git command: {args}"); - - process.Start(); - - var stdOut = process.StandardOutput.ReadToEndAsync(); - var stdErr = process.StandardError.ReadToEndAsync(); - Task.WhenAll(stdOut, stdErr).GetAwaiter().GetResult(); - process.WaitForExit(); - - return (process.ExitCode == 0, stdOut.Result.TrimEnd('\r', '\n'), stdErr.Result); -} - -var gitDir = (new Func(() => -{ - var (success, gitDir, error) = TryRunGitCommand("rev-parse --show-toplevel"); - if (!success) - { - throw new InvalidDataException($"Failed to determine the root of the git repo: {error}"); - } - - return gitDir; -}))(); - -void GenerateDocsImpl(Type type, string baseFile, string outFile, string? categoryName = null) -{ - categoryName ??= type.Name; - var sb = new StringBuilder(); - - Console.WriteLine($"Generating docs for {type}"); - - string baseLuaText; - try - { - baseLuaText = File.ReadAllText(baseFile); - } - catch (FileNotFoundException) - { - baseLuaText = @$"-- luacheck: ignore 111 - ---[[-- -{type.FullName} -]] --- @code {categoryName} --- @pragma nostrip -local {type.Name} = {{}}".ReplaceLineEndings("\n"); - - File.WriteAllText(baseFile, baseLuaText); - } - - var removeTagPattern = new Regex("^-- @remove (.*)$", RegexOptions.Multiline); - var removed = new HashSet(); - var matches = removeTagPattern.Matches(baseLuaText); - - foreach (var match in matches.Cast()) - { - removed.Add(match.Value); - } - - sb.Append(baseLuaText); - sb.AppendLine(); - sb.AppendLine(); - - var members = type.GetMembers(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); - foreach (var member in members) - { - static string EscapeName(string n) - { - return n switch - { - "end" => "endparam", - _ => n - }; - } - - switch (member.MemberType) - { - case MemberTypes.Method: - { - var method = (MethodInfo)member; - - // Exclude property getters/setters - if (method.IsSpecialName) - { - continue; - } - - var paramNames = new StringBuilder(); - foreach (var parameter in method.GetParameters()) - { - paramNames.Append(EscapeName(parameter.Name!)); - paramNames.Append(", "); - } - if (paramNames.Length > 0) - { - // Remove the last separator - paramNames.Length -= 2; - } - - string functionDecoration; - if (method.IsStatic) - { - functionDecoration = $"function {type.Name}.{method.Name}({paramNames}) end"; - } - else - { - functionDecoration = $"function {method.Name}({paramNames}) end"; - } - - if (removed.Contains(functionDecoration)) - { - continue; - } - - Console.WriteLine($" - METHOD: {method}"); - - sb.AppendLine($"--- {method.Name}"); - sb.AppendLine("-- @realm shared"); - - foreach (var parameter in method.GetParameters()) - { - sb.AppendLine($"-- @tparam {TypeToString(parameter.ParameterType)} {EscapeName(parameter.Name!)}"); - } - - if (method.ReturnType != typeof(void)) - { - sb.AppendLine($"-- @treturn {TypeToString(method.ReturnType)}"); - } - - sb.AppendLine(functionDecoration); - sb.AppendLine(); - break; - } - - case MemberTypes.Field: - { - var field = (FieldInfo)member; - - var name = EscapeName(field.Name); - var returnName = TypeToString(field.FieldType); - - if (field.IsStatic) - { - name = type.Name + "." + field.Name; - } - - if (removed.Contains(name)) - { - continue; - } - - Console.WriteLine($" - FIELD: {name}"); - - sb.AppendLine("---"); - sb.Append("-- "); - sb.Append(name); - sb.AppendLine($", field of type {returnName}"); - sb.AppendLine("-- @realm shared"); - sb.AppendLine($"-- @field {name}"); - - sb.AppendLine(); - break; - } - - case MemberTypes.Property: - { - var property = (PropertyInfo)member; - - var name = EscapeName(property.Name); - var returnName = TypeToString(property.PropertyType); - - if (property.GetGetMethod()?.IsStatic == true || property.GetSetMethod()?.IsStatic == true) - { - name = type.Name + "." + property.Name; - } - - if (removed.Contains(name)) - { - continue; - } - - Console.WriteLine($" - PROPERTY: {name}"); - - sb.AppendLine("---"); - sb.Append("-- "); - sb.Append(name); - sb.AppendLine($", field of type {returnName}"); - sb.AppendLine("-- @realm shared"); - sb.AppendLine($"-- @field {name}"); - - sb.AppendLine(); - break; - } - } - } - - new FileInfo(outFile).Directory.Create(); - File.WriteAllText(outFile, sb.ToString()); -} - -var basePath = $"{gitDir}/luacs-docs/lua"; -var generatedDir = $"{basePath}/lua/generated"; -var baseLuaDir = $"{basePath}/baseluadocs"; -void GenerateDocs(Type type, string file, string? categoryName = null) -{ - GenerateDocsImpl(type, $"{baseLuaDir}/{file}", $"{generatedDir}/{file}", categoryName); -} - -try -{ - Directory.Delete(generatedDir, true); -} -catch (DirectoryNotFoundException) { } - -Directory.CreateDirectory(generatedDir); -Directory.CreateDirectory(baseLuaDir); - -GenerateDocs(typeof(Character), "Character.lua"); -GenerateDocs(typeof(CharacterInfo), "CharacterInfo.lua"); -GenerateDocs(typeof(CharacterHealth), "CharacterHealth.lua"); -GenerateDocs(typeof(AnimController), "AnimController.lua"); -GenerateDocs(typeof(Client), "Client.lua"); -GenerateDocs(typeof(Entity), "Entity.lua"); -GenerateDocs(typeof(EntitySpawner), "Entity.Spawner.lua", "Entity.Spawner"); -GenerateDocs(typeof(Item), "Item.lua"); -GenerateDocs(typeof(ItemPrefab), "ItemPrefab.lua"); -GenerateDocs(typeof(Submarine), "Submarine.lua"); -GenerateDocs(typeof(SubmarineInfo), "SubmarineInfo.lua"); -GenerateDocs(typeof(Job), "Job.lua"); -GenerateDocs(typeof(JobPrefab), "JobPrefab.lua"); -GenerateDocs(typeof(GameSession), "GameSession.lua", "Game.GameSession"); -GenerateDocs(typeof(NetLobbyScreen), "NetLobbyScreen.lua", "Game.NetLobbyScreen"); -GenerateDocs(typeof(GameScreen), "GameScreen.lua", "Game.GameScreen"); -GenerateDocs(typeof(FarseerPhysics.Dynamics.World), "World.lua", "Game.World"); -GenerateDocs(typeof(Inventory), "Inventory.lua", "Inventory"); -GenerateDocs(typeof(ItemInventory), "ItemInventory.lua", "ItemInventory"); -GenerateDocs(typeof(CharacterInventory), "CharacterInventory.lua", "CharacterInventory"); -GenerateDocs(typeof(Hull), "Hull.lua", "Hull"); -GenerateDocs(typeof(Level), "Level.lua", "Level"); -GenerateDocs(typeof(Affliction), "Affliction.lua", "Affliction"); -GenerateDocs(typeof(AfflictionPrefab), "AfflictionPrefab.lua", "AfflictionPrefab"); -GenerateDocs(typeof(WayPoint), "WayPoint.lua", "WayPoint"); -GenerateDocs(typeof(ServerSettings), "ServerSettings.lua", "Game.ServerSettings"); -GenerateDocs(typeof(GameSettings), "GameSettings.lua", "Game.Settings"); -GenerateDocs(typeof(Structure), "Structure.lua", "Structure"); -GenerateDocs(typeof(StructurePrefab), "StructurePrefab.lua", "StructurePrefab"); -GenerateDocs(typeof(Limb), "Limb.lua", "Limb"); -GenerateDocs(typeof(PhysicsBody), "PhysicsBody.lua", "PhysicsBody");