LANCommander/LANCommander.CompletionGenerator/Program.cs

252 lines
9.2 KiB
C#

using System.Management.Automation;
using System.Reflection;
using System.Text;
using System.Text.Json;
using LANCommander.SDK.PowerShell.Extensions;
if (args.Length == 0)
{
Console.Error.WriteLine("Usage: LANCommander.CompletionGenerator <output-path>");
return 1;
}
var outputPath = args[0];
var assembly = typeof(InitialSessionStateExtensions).Assembly;
var cmdletTypes = assembly.GetTypes()
.Where(t => t.GetCustomAttribute<CmdletAttribute>() != null)
.OrderBy(t => t.GetCustomAttribute<CmdletAttribute>()!.VerbName + "-" + t.GetCustomAttribute<CmdletAttribute>()!.NounName);
var sb = new StringBuilder();
sb.AppendLine("// Auto-generated by LANCommander.CompletionGenerator — do not edit manually");
sb.AppendLine();
sb.AppendLine("export interface CmdletParameter {");
sb.AppendLine(" name: string;");
sb.AppendLine(" type: string;");
sb.AppendLine(" mandatory: boolean;");
sb.AppendLine(" position: number | null;");
sb.AppendLine(" helpMessage: string | null;");
sb.AppendLine(" aliases: string[];");
sb.AppendLine("}");
sb.AppendLine();
sb.AppendLine("export interface CmdletDefinition {");
sb.AppendLine(" name: string;");
sb.AppendLine(" description: string | null;");
sb.AppendLine(" outputType: string | null;");
sb.AppendLine(" parameters: CmdletParameter[];");
sb.AppendLine("}");
sb.AppendLine();
sb.AppendLine("export const cmdlets: CmdletDefinition[] = [");
foreach (var type in cmdletTypes)
{
var cmdletAttr = type.GetCustomAttribute<CmdletAttribute>()!;
var name = $"{cmdletAttr.VerbName}-{cmdletAttr.NounName}";
var outputTypeAttr = type.GetCustomAttribute<OutputTypeAttribute>();
string? outputType = null;
if (outputTypeAttr?.Type?.Length > 0)
outputType = GetFriendlyTypeName(outputTypeAttr.Type[0].Type);
// Use XML doc summary if available via the Summary property pattern, otherwise use null
var descriptionLines = type.GetCustomAttributes()
.Where(a => a.GetType().Name == "DescriptionAttribute")
.Select(a => a.GetType().GetProperty("Description")?.GetValue(a)?.ToString())
.FirstOrDefault();
// Fall back to XML summary comment — not available via reflection, use class summary convention
string? description = descriptionLines;
sb.AppendLine(" {");
sb.AppendLine($" name: {JsonEncode(name)},");
sb.AppendLine($" description: {JsonEncode(description)},");
sb.AppendLine($" outputType: {JsonEncode(outputType)},");
sb.AppendLine(" parameters: [");
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.GetCustomAttribute<ParameterAttribute>() != null)
.OrderBy(p =>
{
var pa = p.GetCustomAttribute<ParameterAttribute>()!;
return pa.Position == int.MinValue ? int.MaxValue : pa.Position;
})
.ThenBy(p => p.Name);
foreach (var prop in properties)
{
var paramAttr = prop.GetCustomAttribute<ParameterAttribute>()!;
var aliasAttr = prop.GetCustomAttribute<AliasAttribute>();
var aliases = aliasAttr?.AliasNames?.ToArray() ?? Array.Empty<string>();
var position = paramAttr.Position == int.MinValue ? (int?)null : paramAttr.Position;
sb.AppendLine(" {");
sb.AppendLine($" name: {JsonEncode(prop.Name)},");
sb.AppendLine($" type: {JsonEncode(GetFriendlyTypeName(prop.PropertyType))},");
sb.AppendLine($" mandatory: {(paramAttr.Mandatory ? "true" : "false")},");
sb.AppendLine($" position: {(position.HasValue ? position.Value.ToString() : "null")},");
sb.AppendLine($" helpMessage: {JsonEncode(paramAttr.HelpMessage)},");
sb.AppendLine($" aliases: [{string.Join(", ", aliases.Select(a => JsonEncode(a)))}],");
sb.AppendLine(" },");
}
sb.AppendLine(" ],");
sb.AppendLine(" },");
}
sb.AppendLine("];");
sb.AppendLine();
// Generate type definitions for complex objects used as script variables
var variableTypes = new Dictionary<string, Type>
{
["GameManifest"] = typeof(LANCommander.SDK.Models.Manifest.Game),
["ToolManifest"] = typeof(LANCommander.SDK.Models.Manifest.Tool),
["RedistributableManifest"] = typeof(LANCommander.SDK.Models.Manifest.Redistributable),
["Server"] = typeof(LANCommander.SDK.Models.Server),
["Game"] = typeof(LANCommander.SDK.Models.Game),
["User"] = typeof(LANCommander.SDK.Models.User),
["Tool"] = typeof(LANCommander.SDK.Models.Tool),
["Redistributable"] = typeof(LANCommander.SDK.Models.Redistributable),
};
sb.AppendLine("export interface TypeProperty {");
sb.AppendLine(" name: string;");
sb.AppendLine(" type: string;");
sb.AppendLine("}");
sb.AppendLine();
sb.AppendLine("export interface TypeDefinition {");
sb.AppendLine(" name: string;");
sb.AppendLine(" properties: TypeProperty[];");
sb.AppendLine("}");
sb.AppendLine();
sb.AppendLine("export const variableTypes: TypeDefinition[] = [");
foreach (var (typeName, clrType) in variableTypes.OrderBy(kv => kv.Key))
{
sb.AppendLine(" {");
sb.AppendLine($" name: {JsonEncode(typeName)},");
sb.AppendLine(" properties: [");
var props = clrType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead)
.OrderBy(p => p.Name);
foreach (var prop in props)
{
sb.AppendLine(" {");
sb.AppendLine($" name: {JsonEncode(prop.Name)},");
sb.AppendLine($" type: {JsonEncode(GetPropertyTypeName(prop.PropertyType))},");
sb.AppendLine(" },");
}
sb.AppendLine(" ],");
sb.AppendLine(" },");
}
sb.AppendLine("];");
sb.AppendLine();
// Generate ScriptType enum values
sb.AppendLine("export const scriptTypeValues: string[] = [");
foreach (var value in Enum.GetNames<LANCommander.SDK.Enums.ScriptType>())
{
sb.AppendLine($" {JsonEncode(value)},");
}
sb.AppendLine("];");
var directory = Path.GetDirectoryName(outputPath);
if (!string.IsNullOrEmpty(directory))
Directory.CreateDirectory(directory);
File.WriteAllText(outputPath, sb.ToString(), Encoding.UTF8);
Console.WriteLine($"Generated completions for {cmdletTypes.Count()} cmdlets and {variableTypes.Count} variable types -> {outputPath}");
return 0;
static string JsonEncode(string? value)
{
if (value == null) return "null";
return JsonSerializer.Serialize(value);
}
static string GetPropertyTypeName(Type type)
{
var underlying = Nullable.GetUnderlyingType(type);
if (underlying != null)
return GetPropertyTypeName(underlying) + "?";
if (type.IsArray)
return GetPropertyTypeName(type.GetElementType()!) + "[]";
if (type.IsGenericType)
{
var genericDef = type.GetGenericTypeDefinition();
if (genericDef == typeof(IEnumerable<>) || genericDef == typeof(ICollection<>) ||
genericDef == typeof(List<>) || genericDef == typeof(IList<>))
{
var elementType = type.GetGenericArguments()[0];
return GetPropertyTypeName(elementType) + "[]";
}
}
// Check non-generic IEnumerable
if (type != typeof(string) && type.GetInterfaces().Any(i =>
i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
{
var elementType = type.GetInterfaces()
.First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.GetGenericArguments()[0];
return GetPropertyTypeName(elementType) + "[]";
}
if (type == typeof(string)) return "string";
if (type == typeof(int) || type == typeof(long)) return "int";
if (type == typeof(uint)) return "uint";
if (type == typeof(double) || type == typeof(float)) return "double";
if (type == typeof(bool)) return "bool";
if (type == typeof(Guid)) return "Guid";
if (type == typeof(DateTime)) return "DateTime";
if (type == typeof(Uri)) return "Uri";
if (type.IsEnum) return type.Name;
return type.Name;
}
static string GetFriendlyTypeName(Type? type)
{
if (type == null) return "object";
// Handle nullable types
var underlying = Nullable.GetUnderlyingType(type);
if (underlying != null)
return GetFriendlyTypeName(underlying) + "?";
// Handle arrays
if (type.IsArray)
return GetFriendlyTypeName(type.GetElementType()) + "[]";
// Handle SwitchParameter
if (type == typeof(SwitchParameter))
return "SwitchParameter";
// Handle common types
if (type == typeof(string)) return "string";
if (type == typeof(int)) return "int";
if (type == typeof(long)) return "long";
if (type == typeof(uint)) return "uint";
if (type == typeof(double)) return "double";
if (type == typeof(float)) return "float";
if (type == typeof(bool)) return "bool";
if (type == typeof(byte)) return "byte";
if (type == typeof(byte[])) return "byte[]";
if (type == typeof(Guid)) return "Guid";
if (type == typeof(Uri)) return "Uri";
if (type == typeof(object)) return "object";
// Handle SecureString
if (type == typeof(System.Security.SecureString))
return "SecureString";
return type.Name;
}