File: HelpGenerationExtensions.cs
Web Access
Project: ..\..\..\src\System.CommandLine.StaticCompletions\System.CommandLine.StaticCompletions.csproj (System.CommandLine.StaticCompletions)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
namespace System.CommandLine.StaticCompletions;
 
using System.CommandLine;
 
public static class HelpExtensions
{
    /// <summary>
    /// Create a unique shell function name for a command - these names should be
    /// * distinct from the 'root' command's name (i.e. we should not generate the function name 'dotnet' for the binary 'dotnet')
    /// * distinct based on 'path' to get to this function (hence the parentCommandNames)
    /// </summary>
    /// <param name="command"></param>
    /// <param name="parentCommandNames">The chain of commands to get to this command</param>
    /// <returns></returns>
    public static string FunctionName(this Command command, string[]? parentCommandNames = null) => parentCommandNames switch
    {
        null => "_" + command.Name,
        [] => "_" + command.Name,
        var names => "_" + string.Join('_', names) + "_" + command.Name
    };
 
    /// <summary>
    /// Sanitizes a function name to be safe for bash
    /// </summary>
    /// <param name="functionName"></param>
    /// <returns></returns>
    public static string MakeSafeFunctionName(this string functionName) => functionName.Replace('-', '_');
 
    /// <summary>
    /// Get all names for an option, including the primary name and all aliases
    /// </summary>
    /// <param name="option"></param>
    /// <returns></returns>
    public static string[] Names(this Option option)
    {
        var (primary, aliases) = PrimaryNameAndAliases(option);
        return aliases is null ? [primary] : [primary, .. aliases];
    }
 
    public static (string primary, string[]? aliases) PrimaryNameAndAliases(this Option option)
    {
        if (option.Aliases.Count == 0)
        {
            return (option.Name, null);
        }
        else if (option is System.CommandLine.Help.HelpOption) // some of the help aliases are truly horrible
        {
            return ("--help", ["-h"]);
        }
        else
        {
            return (option.Name, [.. option.Aliases]);
        }
    }
 
    /// <summary>
    /// Get all names for a command, including the primary name and all aliases
    /// </summary>
    /// <param name="command"></param>
    /// <returns></returns>
    public static string[] Names(this Command command)
    {
        if (command.Aliases.Count == 0)
        {
            return [command.Name];
        }
        else
        {
            return [command.Name, .. command.Aliases];
        }
    }
 
    public static IEnumerable<Option> HierarchicalOptions(this Command c)
    {
        // don't include hidden options, because hidden shouldn't be shown in completions at all.
        var myOptions = c.Options.Where(o => !o.Hidden);
        if (c.Parents.Count() == 0)
        {
            return myOptions;
        }
        else
        {
            // the parents could return the same logical option, so we need to dedupe them in order to not crowd the completion lists.
            return myOptions.Concat(c.Parents.OfType<Command>().SelectMany(OptionsForParent)).DistinctBy(o => o.Name);
        }
    }
 
    private static IEnumerable<Option> OptionsForParent(Command c)
    {
        foreach (var o in c.Options)
        {
            if (o.Recursive && !o.Hidden)
            {
                yield return o;
            }
        }
        foreach (var p in c.Parents.OfType<Command>())
        {
            foreach (var o in OptionsForParent(p))
            {
                yield return o;
            }
        }
    }
 
    public static bool IsUpperCaseSingleCharacterFlag(this string name) => name.Length == 2 && char.IsUpper(name[1]);
 
    public static bool IsLongAlias(this string name) => name.Length > 2 && name[0] == '-' && name[1] == '-';
    public static bool IsShortAlias(this string name) => name.Length == 2 && name[0] == '-' && char.IsAsciiLetter(name[1]);
 
    public static bool IsDynamic(this Option option) => option.GetType().GetInterface(nameof(IDynamicOption)) is not null;
    public static bool IsDynamic(this Argument argument) => argument.GetType().GetInterface(nameof(IDynamicArgument)) is not null;
 
    public static bool IsFlag(this Option option) => option.Arity.Equals(ArgumentArity.Zero);
}