using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using CliFx.Binding; using CliFx.Infrastructure; namespace CliFx.Help; internal class HelpWriter(HelpContext context, ConsoleWriter consoleWriter) : FormattedConsoleWriter(consoleWriter) { private void WriteHeader(string text) { Write(ConsoleColor.White, text.ToUpperInvariant()); WriteLine(); } private void WriteCommandInvocation() { Write(context.Metadata.ExecutableName); // Command name if (!string.IsNullOrWhiteSpace(context.Command.Name)) { Write(' '); Write(ConsoleColor.Cyan, context.Command.Name); } } private void WriteCommandUsage() { if (!IsEmpty) WriteVerticalMargin(); WriteHeader("Usage"); // Current command { WriteHorizontalMargin(); WriteCommandInvocation(); Write(' '); // Parameters foreach (var parameter in context.Command.Parameters.OrderBy(p => p.Order)) { Write(ConsoleColor.DarkCyan, parameter.ToString(includeKind: false)); Write(' '); } // Required options foreach (var option in context.Command.Options.Where(o => o.IsRequired)) { Write( ConsoleColor.Yellow, option.ToString( includeKind: false, includeBothIdentifiers: false, includeValuePlaceholder: true ) ); Write(' '); } // Placeholder for non-required options if (context.Command.Options.Any(o => !o.IsRequired)) { Write(ConsoleColor.Yellow, "[options]"); } WriteLine(); } // Examples foreach (var example in context.Command.Examples) { WriteHorizontalMargin(); WriteCommandInvocation(); Write(' '); Write(example); WriteLine(); } // Child commands if (context.Root.GetChildCommands(context.Command.Name).Any()) { WriteHorizontalMargin(); WriteCommandInvocation(); Write(' '); // Placeholder for child command Write(ConsoleColor.Cyan, "[command]"); Write(' '); // Placeholder for other arguments Write("..."); WriteLine(); } } private void WriteCommandDescription() { if (string.IsNullOrWhiteSpace(context.Command.Description)) return; if (!IsEmpty) WriteVerticalMargin(); WriteHeader("Description"); WriteHorizontalMargin(); Write(context.Command.Description); WriteLine(); } private void WriteCommandInputDefaultValue(CommandInputDescriptor input) { var defaultValue = context.CommandDefaultValues.GetValueOrDefault(input); if (defaultValue is null) return; // Normalize to an array to process both scalar and sequence default values uniformly var defaultValues = defaultValue is not string && defaultValue is IEnumerable defaultValueAsEnumerable ? defaultValueAsEnumerable.Cast().ToArray() : [defaultValue]; // Only strings, chars, bools, and types that implement IFormattable have // meaningful ToString() representations. if (!defaultValues.All(v => v is string or char or bool or IFormattable)) return; var isFirst = true; foreach (var value in defaultValues) { if (isFirst) { Write(ConsoleColor.White, "Default: "); isFirst = false; } else { Write(", "); } Write(ConsoleColor.DarkGray, '<'); Write( value switch { // Lowercase bools look better bool boolValue => boolValue.ToString().ToLowerInvariant(), IFormattable formattable => formattable.ToString( null, CultureInfo.InvariantCulture ), _ => value.ToString(), } ); Write(ConsoleColor.DarkGray, '>'); } if (!isFirst) Write('.'); } private void WriteCommandParameters() { if (!context.Command.Parameters.Any()) return; if (!IsEmpty) WriteVerticalMargin(); WriteHeader("Parameters"); foreach (var parameter in context.Command.Parameters.OrderBy(p => p.Order)) { if (parameter.IsRequired) { Write(ConsoleColor.Red, "* "); } else { WriteHorizontalMargin(); } Write(ConsoleColor.DarkCyan, $"{parameter.Name}"); WriteColumnMargin(); // Description if (!string.IsNullOrWhiteSpace(parameter.Description)) { Write(parameter.Description); Write(' '); } // Valid values var validValues = parameter.Property.TryGetValidValues() ?? []; if (validValues.Any()) { Write(ConsoleColor.White, "Choices: "); var isFirst = true; foreach (var validValue in validValues) { if (validValue is null) continue; if (isFirst) { isFirst = false; } else { Write(", "); } Write('"'); Write(validValue.ToString()); Write('"'); } Write('.'); Write(' '); } // Default value if (!parameter.IsRequired) { WriteCommandInputDefaultValue(parameter); } WriteLine(); } } private void WriteCommandOptions() { if (!IsEmpty) WriteVerticalMargin(); WriteHeader("Options"); foreach (var option in context.Command.Options.OrderByDescending(o => o.IsRequired)) { if (option.IsRequired) { Write(ConsoleColor.Red, "* "); } else { WriteHorizontalMargin(); } // Short name if (option.ShortName is not null) { Write(ConsoleColor.Yellow, $"-{option.ShortName}"); } // Separator if (!string.IsNullOrWhiteSpace(option.Name) && option.ShortName is not null) { Write('|'); } // Name if (!string.IsNullOrWhiteSpace(option.Name)) { Write(ConsoleColor.Yellow, $"--{option.Name}"); } WriteColumnMargin(); // Description if (!string.IsNullOrWhiteSpace(option.Description)) { Write(option.Description); Write(' '); } // Valid values var validValues = option.Property.TryGetValidValues() ?? []; if (validValues.Any()) { Write(ConsoleColor.White, "Choices: "); var isFirst = true; foreach (var validValue in validValues) { if (validValue is null) continue; if (isFirst) { isFirst = false; } else { Write(", "); } Write('"'); Write(validValue.ToString()); Write('"'); } Write('.'); Write(' '); } // Environment variable if (!string.IsNullOrWhiteSpace(option.EnvironmentVariable)) { Write(ConsoleColor.White, "Environment variable: "); Write(option.EnvironmentVariable); Write('.'); Write(' '); } // Default value if (!option.IsRequired) { WriteCommandInputDefaultValue(option); } WriteLine(); } } private void WriteCommandChildren() { var childCommands = context .Root.GetChildCommands(context.Command.Name) .OrderBy(a => a.Name, StringComparer.Ordinal) .ToArray(); if (!childCommands.Any()) return; if (!IsEmpty) WriteVerticalMargin(); WriteHeader("Commands"); foreach (var childCommand in childCommands) { // Name WriteHorizontalMargin(); Write( ConsoleColor.Cyan, // Trim the name so it's relative to the current command childCommand.Name?.Substring(context.Command.Name?.Length ?? 0).Trim() ); WriteColumnMargin(); // Description if (!string.IsNullOrWhiteSpace(childCommand.Description)) { Write(childCommand.Description); Write(' '); } // Grand-child commands var grandChildCommands = context .Root.GetChildCommands(childCommand.Name) .OrderBy(c => c.Name, StringComparer.Ordinal) .ToArray(); if (grandChildCommands.Any()) { Write(ConsoleColor.White, "Subcommands: "); var isFirst = true; foreach (var grandChildCommand in grandChildCommands) { if (isFirst) { isFirst = false; } else { Write(", "); } Write( ConsoleColor.Cyan, // Trim the name so it's relative to the current command grandChildCommand.Name?.Substring(context.Command.Name?.Length ?? 0).Trim() ); } Write('.'); } WriteLine(); } // Child command help tip WriteVerticalMargin(); Write("You can run \""); WriteCommandInvocation(); Write(' '); Write(ConsoleColor.Cyan, "[command]"); Write(' '); Write(ConsoleColor.White, "--help"); Write("\" to show help for a specific command."); WriteLine(); } public void WriteHelpText() { // Application info Write(ConsoleColor.White, context.Metadata.Title); Write(' '); Write(ConsoleColor.Yellow, context.Metadata.Version); WriteLine(); if (!string.IsNullOrWhiteSpace(context.Metadata.Description)) { WriteHorizontalMargin(); Write(context.Metadata.Description); WriteLine(); } // Command info WriteCommandUsage(); WriteCommandDescription(); WriteCommandParameters(); WriteCommandOptions(); WriteCommandChildren(); } }