File: Helpers\ConsoleTable.cs
Web Access
Project: src\src\Tools\dotnet-user-jwts\src\dotnet-user-jwts.csproj (dotnet-user-jwts)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Data;
using System.Globalization;
using System.Linq;
using System.Text;
using Microsoft.Extensions.Tools.Internal;
 
namespace Microsoft.Extensions.CommandLineUtils;
 
internal sealed class ConsoleTable
{
    private readonly List<string> _columns = new();
    private readonly List<string[]> _rows = new();
    private readonly IReporter _reporter;
 
    public ConsoleTable(IReporter reporter)
    {
        _reporter = reporter;
    }
 
    public void AddColumns(params string[] names)
    {
        _columns.AddRange(names);
    }
 
    public void AddRow(params string[] values)
    {
        ArgumentNullException.ThrowIfNull(values);
 
        if (!_columns.Any())
        {
            throw new InvalidOperationException("Columns must be set before rows can be added.");
        }
 
        if (_columns.Count != values.Length)
        {
            throw new InvalidOperationException(
                $"The number of columns in the table '{_columns.Count}' does not match the number of columns in the row '{values.Length}'.");
        }
 
        _rows.Add(values);
    }
 
    public void Write()
    {
        var builder = new StringBuilder();
 
        var maxColumnLengths = _columns
            .Select((t, i) => _rows.Select(x => x[i])
                .Concat(new[] { _columns[i] })
                .Where(x => x != null)
                .Select(x => x!.ToString()!.Length).Max())
            .ToList();
 
        // The table borders constructed using "|" have whitespaces before and after.
        // This number accounts for those spaces to ensure that the table width is not longer than the console window's width.
        var EXCESS_LENGTH_CREATED_BY_BORDERS = 4;
 
        var equalColumnLengths = Math.Max((Console.WindowWidth / _columns.Count) - EXCESS_LENGTH_CREATED_BY_BORDERS, 5);
 
        var excessLength = 0;
        var numberOfColumnsThatNeedMoreLength = 0;
 
        // Keep track of the excess length left behind by narrow columns and the number of columns that could use the extra length.
        for (var i = 0; i < maxColumnLengths.Count; i++)
        {
            if (maxColumnLengths[i] < equalColumnLengths)
            {
                excessLength += equalColumnLengths - maxColumnLengths[i];
            }
            else
            {
                numberOfColumnsThatNeedMoreLength += 1;
                maxColumnLengths[i] = equalColumnLengths;
            }
        }
 
        // Share the excess length amongst the columns that could use it.
        for (var i = 0; i < maxColumnLengths.Count; i++)
        {
            if (maxColumnLengths[i] == equalColumnLengths)
            {
                maxColumnLengths[i] += excessLength / numberOfColumnsThatNeedMoreLength;
            }
        }
 
        var formatRow = Enumerable.Range(0, _columns.Count)
            .Select(i => " | {" + i + ", " + maxColumnLengths[i] + "}")
            .Aggregate((previousRowColumn, nextRowColumn) => previousRowColumn + nextRowColumn) + " |";
 
        var columnHeaders = string.Format(CultureInfo.InvariantCulture, formatRow, _columns.ToArray());
        var rowDivider = $" {new string('-', columnHeaders.Length - 1)} ";
 
        builder.AppendLine(rowDivider);
        builder.AppendLine(columnHeaders);
 
        WriteTableContent(_rows, maxColumnLengths, rowDivider, builder);
 
        void WriteTableContent(List<string[]> rows, List<int> columnLengths, string divider, StringBuilder stringBuilder)
        {
            stringBuilder.AppendLine(divider);
 
            for (var i = 0; i < rows.Count; i++)
            {
                while (!rows[i].All(i => i == string.Empty))
                {
                    var outputRow = string.Empty;
 
                    for (var j = 0; j < rows[i].Length; j++)
                    {
                        outputRow = string.Concat(outputRow, " | ");
 
                        if (rows[i][j].Length <= columnLengths[j])
                        {
                            outputRow = string.Concat(outputRow, rows[i][j], new string(' ', columnLengths[j] - rows[i][j].Length));
                            rows[i][j] = string.Empty;
                        }
                        else
                        {
                            outputRow = string.Concat(outputRow, rows[i][j].Substring(0, columnLengths[j]));
                            rows[i][j] = rows[i][j].Substring(columnLengths[j]);
                        }
                    }
                    outputRow = string.Concat(outputRow, " |");
                    stringBuilder.AppendLine(outputRow);
                }
                stringBuilder.AppendLine(divider);
            }
            _reporter.Output(builder.ToString());
        }
    }
}