Code/SboxSuperuser/Core/Bootstrap/StartupBanner.cs

Console banner helper for the Superuser server-admin package. Builds the ASCII logo header with version and owner count, one line per loaded or failed module from ModuleReport, and a closing 'Ready' summary line pointing at the help command.

using System.Text;

namespace SboxSuperuser.Core.Bootstrap;

/// <summary>The text Superuser prints to the console when it starts and as its modules load.</summary>
internal static class StartupBanner
{
	// ANSI Shadow, the block-letter style most server consoles render cleanly.
	private static readonly string[] Logo =
	{
		"███████╗██╗   ██╗██████╗ ███████╗██████╗ ██╗   ██╗███████╗███████╗██████╗ ",
		"██╔════╝██║   ██║██╔══██╗██╔════╝██╔══██╗██║   ██║██╔════╝██╔════╝██╔══██╗",
		"███████╗██║   ██║██████╔╝█████╗  ██████╔╝██║   ██║███████╗█████╗  ██████╔╝",
		"╚════██║██║   ██║██╔═══╝ ██╔══╝  ██╔══██╗██║   ██║╚════██║██╔══╝  ██╔══██╗",
		"███████║╚██████╔╝██║     ███████╗██║  ██║╚██████╔╝███████║███████╗██║  ██║",
		"╚══════╝ ╚═════╝ ╚═╝     ╚══════╝╚═╝  ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚═╝  ╚═╝"
	};

	/// <summary>The logo with the version line under it and where the owners come from.</summary>
	internal static string Header( string version, string author, int owners, string ownerFile )
	{
		var text = new StringBuilder();
		text.AppendLine();
		foreach ( var line in Logo ) text.AppendLine( "  " + line );
		text.AppendLine();
		text.AppendLine( $"  Server administration for s&box · v{version} by {author}" );
		text.Append( owners == 0
			? $"  No owners yet: add your Steam ID to {ownerFile}"
			: $"  {owners} owner{Plural( owners )} from {ownerFile}" );
		return text.ToString();
	}

	/// <summary>One line per module: a check and what it added, or a cross and why it didn't load.</summary>
	internal static string Module( ModuleReport module )
	{
		if ( !module.Loaded ) return $"  ✗ {module.Id}: {module.Problem}";
		var who = module.Version.Length > 0 ? $" {module.Version}" : "";
		if ( module.Author.Length > 0 ) who += $" by {module.Author}";
		var parts = new List<string>
		{
			Count( module.Commands, "command" ),
			Count( module.Permissions, "permission" )
		};
		if ( module.Pages > 0 ) parts.Add( Count( module.Pages, "guide page" ) );
		if ( module.Windows > 0 ) parts.Add( Count( module.Windows, "menu window" ) );
		return $"  ✓ {module.Id}{who} · {string.Join( " · ", parts )}";
	}

	/// <summary>The closing line once every module has had its turn.</summary>
	internal static string Ready( IReadOnlyCollection<ModuleReport> modules, string root )
	{
		var loaded = modules.Count( module => module.Loaded );
		var failed = modules.Count - loaded;
		var commands = modules.Where( module => module.Loaded ).Sum( module => module.Commands );
		var summary = $"Ready: {Count( loaded, "module" )}, {Count( commands, "command" )}";
		if ( failed > 0 ) summary += $", {failed} not loaded (see above)";
		return $"  {summary}. Type \"{root} help\" in the console or \"/{root} help\" in chat.";
	}

	private static string Count( int count, string noun ) => $"{count} {noun}{Plural( count )}";

	private static string Plural( int count ) => count == 1 ? "" : "s";
}