using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading; using ExportCommands.Properties; // ReSharper disable LocalizableElement namespace ExportCommands { internal class Program { private static readonly string[] _locales = { "en-US", "it-IT", "pl-PL", "ko-KR" }; private static void Main( string[] args ) { if ( args.Length == 0 ) { Console.WriteLine( "No file specified..." ); return; } if ( !File.Exists( args[0] ) ) { throw new FileNotFoundException( "File specified does not exist.", args[0] ); } AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve; string originalDirectory = Environment.CurrentDirectory; Environment.CurrentDirectory = Path.GetDirectoryName( args[0] ) ?? throw new InvalidOperationException(); string docPath = Path.Combine( originalDirectory, "Docs" ); if ( !Directory.Exists( docPath ) ) { Directory.CreateDirectory( docPath ); } Stopwatch sw = new Stopwatch(); sw.Start(); foreach ( string locale in _locales ) { Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo( locale ); Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo( locale ); Assembly assembly; try { assembly = Assembly.LoadFile( args[0] ); } catch ( Exception e ) { Console.WriteLine( $"Failed to load assembly: {e}" ); return; } Console.WriteLine( "Generating Macro documentation..." ); Console.WriteLine( $"Loading {args[0]}" ); IEnumerable types = assembly.GetTypes().Where( t => t.Namespace != null && t.Namespace.StartsWith( "ClassicAssist.Data.Macros.Commands" ) ); Type cda = assembly.GetType( "ClassicAssist.Data.Macros.CommandsDisplayAttribute" ); Type cdsaa = assembly.GetType( "ClassicAssist.Data.Macros.CommandsDisplayStringSeeAlsoAttribute" ); List commands = new List(); List seeAlsoTypes = new List(); // ReSharper disable once LoopCanBeConvertedToQuery foreach ( Type type in types ) { // ReSharper disable once LoopCanBeConvertedToQuery foreach ( MemberInfo memberInfo in type.GetMembers( BindingFlags.Public | BindingFlags.Static ) ) { Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; Attribute attrCDLN = memberInfo.GetCustomAttribute( cda ); Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo( locale ); Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo( locale ); Attribute attrCD = memberInfo.GetCustomAttribute( cda ); Attribute attrCDSA = memberInfo.GetCustomAttribute( cdsaa ); if ( attrCDSA != null ) { } if ( attrCD == null ) { continue; } List param = new List(); dynamic attrParams = ( (dynamic) attrCD ).Parameters; dynamic attr2Params = null; if ( attrCDSA != null ) { attr2Params = ( (dynamic) attrCDSA ).Enums; } if ( memberInfo.MemberType == MemberTypes.Method && attrParams != null ) { MethodInfo methodInfo = memberInfo as MethodInfo; ParameterInfo[] parameters = methodInfo?.GetParameters(); int i = 0; if ( parameters != null ) { foreach ( ParameterInfo parameterInfo in parameters ) { Parameter parameter = new Parameter(); object defaultValue = parameterInfo.RawDefaultValue; if ( i + 1 > attrParams.Length ) { parameter.Name = parameterInfo.Name.ToLower(); parameter.Optional = defaultValue == null || defaultValue.GetType() != typeof( DBNull ); parameter.Description = Resources.ResourceManager.GetString( "PARAMETER_DESCRIPTION_UNKNOWN" ); param.Add( parameter ); i++; } else { dynamic attrParam = attrParams[i]; if ( attrParam == null ) { continue; } parameter.Name = parameterInfo.Name.ToLower(); parameter.Optional = defaultValue == null || defaultValue.GetType() != typeof( DBNull ); string resourceName = $"PARAMETER_DESCRIPTION_{attrParam.ToUpper()}"; string resourceValue = Resources.ResourceManager.GetString( resourceName ); if ( string.IsNullOrEmpty( resourceValue ) ) { throw new InvalidOperationException( resourceName ); } parameter.Description = !string.IsNullOrEmpty( resourceValue ) ? resourceValue : Resources.Unknown; if ( attr2Params != null && attr2Params?.Length >= i + 1 && attr2Params?[i] != null ) { dynamic typeSA = FindEnumType( attr2Params?[i], assembly ); parameter.SeeAlso = typeSA; if ( typeSA != null && !seeAlsoTypes.Contains( typeSA ) ) { seeAlsoTypes.Add( typeSA ); } } param.Add( parameter ); i++; } } } } commands.Add( new Commands { Category = ( (dynamic) attrCD ).Category, CategoryLN = ( (dynamic) attrCDLN ).Category, Description = ( (dynamic) attrCD ).Description, Example = ( (dynamic) attrCD ).Example, InsertText = ( (dynamic) attrCD ).InsertText, Signature = memberInfo.ToString(), Name = memberInfo.Name, Parameters = param } ); } } commands.Sort( new CommandComparer() ); // Localized, Language Neutral Category IEnumerable<(string L, string LN)> categories = commands.Select( c => (c.Category, c.CategoryLN) ).Distinct(); FileVersionInfo fvi = FileVersionInfo.GetVersionInfo( assembly.Location ); string markDown = $"# {Resources.ClassicAssist_Macro_Commands} \n{Resources.Generated_on} {DateTime.UtcNow} \n{Resources.Version}: {fvi.ProductVersion} \n \n"; if ( !string.IsNullOrEmpty( Resources.TRANSLATE_CREDIT ) ) { markDown = $"# {Resources.ClassicAssist_Macro_Commands} \n{Resources.Generated_on} {DateTime.UtcNow} \n{Resources.Version}: {fvi.ProductVersion} \n{Resources.TRANSLATE_CREDIT} \n \n"; } foreach ( var category in categories ) { IEnumerable categoryCommands = commands.Where( c => c.Category == category.L ).OrderBy( c => c.Name ); GenerateCategory( assembly, category.L, category.LN, categoryCommands, seeAlsoTypes, locale ); string categoryFileName = $"{RemoveAccents( category.LN )}-{locale}.md".Replace( ' ', '-' ); if ( locale.Equals( "en-US" ) ) { categoryFileName = $"{category.LN}.md"; } markDown += $"## [{category.L}]({Path.GetFileNameWithoutExtension( categoryFileName )}) \n"; foreach ( Commands categoryCommand in categoryCommands ) { markDown += $"[{categoryCommand.Name}]({Path.GetFileNameWithoutExtension( categoryFileName )}#{categoryCommand.Name}) \n"; } } //markDown += $"## {Resources.Types} \n"; //seeAlsoTypes = seeAlsoTypes.OrderBy( t => t.Name ).ToList(); //foreach ( Type seeAlsoType in seeAlsoTypes ) //{ // markDown += $"### {seeAlsoType.Name} \n"; // string[] enumNames = seeAlsoType.GetEnumNames(); // if ( enumNames == null ) // { // continue; // } // markDown = enumNames.Aggregate( markDown, ( current, enumName ) => current + $"* {enumName} \n" ); // markDown += " \n"; //} string fileName = $"Macro-Commands ({locale}).md"; if ( locale.Equals( "en-US" ) ) { fileName = "Macro-Commands.md"; } File.WriteAllText( Path.Combine( originalDirectory, "Docs", fileName ), markDown ); } sw.Stop(); Console.WriteLine( $"Finished in {sw.Elapsed}" ); } private static void GenerateCategory( Assembly assembly, string category, string categoryLN, IEnumerable categoryCommands, List seeAlsoTypes, string locale ) { FileVersionInfo fvi = FileVersionInfo.GetVersionInfo( assembly.Location ); string markDown = $"# {Resources.ClassicAssist_Macro_Commands} \n{Resources.Generated_on} {DateTime.UtcNow} \n{Resources.Version}: {fvi.ProductVersion} \n \n"; if ( !string.IsNullOrEmpty( Resources.TRANSLATE_CREDIT ) ) { markDown = $"# {Resources.ClassicAssist_Macro_Commands} \n{Resources.Generated_on} {DateTime.UtcNow} \n{Resources.Version}: {fvi.ProductVersion} \n{Resources.TRANSLATE_CREDIT} \n \n"; } markDown += $"## {category} \n"; List seeAlsoNames = new List(); foreach ( Commands command in categoryCommands ) { string example = command.InsertText; if ( !string.IsNullOrEmpty( command.Example ) ) { example = command.Example; } markDown += $"### {command.Name} \n \n"; markDown += $"{Resources.Method_Signature}: \n \n**{command.Signature}** \n \n"; if ( command.Parameters.Any() ) { markDown += $"#### {Resources.Parameters} \n"; foreach ( Parameter parameter in command.Parameters ) { markDown += $"* {parameter.Name}: {parameter.Description}.{( parameter.Optional ? $" ({Resources.Optional})" : "" )}"; if ( parameter.SeeAlso != null ) { if ( !seeAlsoNames.Contains( parameter.SeeAlso.Name ) ) { seeAlsoNames.Add( parameter.SeeAlso.Name ); } markDown += string.Format( $" {Resources.See_Also___0_} \n", $"[{parameter.SeeAlso.Name}](#{parameter.SeeAlso.Name})" ); } else { markDown += " \n"; } } markDown += " \n"; } markDown += $"{Resources.Description}: \n \n**{command.Description}** \n \n"; markDown += $"{Resources.Example}: \n \n```python \n{example} \n``` \n \n"; } markDown += "\n\n\n"; IOrderedEnumerable includedSeeAlso = seeAlsoTypes.Where( i => seeAlsoNames.Contains( i.Name ) ).OrderBy( i => i.Name ); if ( includedSeeAlso.Any() ) { markDown += $"## {Resources.Types} \n"; foreach ( Type seeAlsoType in includedSeeAlso ) { markDown += $"### {seeAlsoType.Name} \n"; string[] enumNames = seeAlsoType.GetEnumNames(); if ( enumNames == null ) { continue; } markDown = enumNames.Aggregate( markDown, ( current, enumName ) => current + $"* {enumName} \n" ); markDown += " \n"; } } string fileName = $"{RemoveAccents( categoryLN )}-{locale}.md".Replace( ' ', '-' ); if ( locale.Equals( "en-US" ) ) { fileName = $"{categoryLN}.md"; } File.WriteAllText( Path.Combine( Environment.CurrentDirectory, "Docs", fileName ), markDown ); } private static Type FindEnumType( string shortName, Assembly assembly ) { Type matchingType = assembly.GetTypes().FirstOrDefault( t => t.Name == shortName ); return matchingType; } private static Assembly OnAssemblyResolve( object sender, ResolveEventArgs args ) { string assemblyname = new AssemblyName( args.Name ).Name; string[] searchPaths = { Environment.CurrentDirectory, RuntimeEnvironment.GetRuntimeDirectory() }; if ( assemblyname.Contains( "Colletions" ) ) { assemblyname = "System.Collections"; } foreach ( string searchPath in searchPaths ) { string fullPath = Path.Combine( searchPath, assemblyname + ".dll" ); string culture = new AssemblyName( args.Name ).CultureName; if ( !File.Exists( fullPath ) ) { string culturePath = Path.Combine( searchPath, culture, assemblyname + ".dll" ); if ( File.Exists( culturePath ) ) { fullPath = culturePath; } else { continue; } } Assembly assembly = Assembly.LoadFrom( fullPath ); Console.WriteLine( $"Loading {fullPath}" ); return assembly; } Console.WriteLine( $"Couldn't locate {assemblyname}..." ); return null; } private static string RemoveAccents( string input ) { byte[] tempBytes = Encoding.GetEncoding( "ISO-8859-8" ).GetBytes( input ); return Encoding.UTF8.GetString( tempBytes ); } } internal class CommandComparer : IComparer { public int Compare( Commands x, Commands y ) { int result = string.Compare( x?.Category, y?.Category, StringComparison.InvariantCultureIgnoreCase ); return result; } } internal class Parameter { public string Description { get; set; } = "Unknown"; public string Name { get; set; } = "Unknown"; public bool Optional { get; set; } public Type SeeAlso { get; set; } } internal class Commands { public string Category { get; set; } public string Description { get; set; } public string Example { get; set; } public string InsertText { get; set; } public string Name { get; set; } public List Parameters { get; set; } public string Signature { get; set; } public string CategoryLN { get; set; } } }