Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
TableWriter.cs654 linesDownload Raw Back to common
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections.Generic;6using System.Collections.Specialized;7using System.Management.Automation;8using System.Management.Automation.Internal;9using System.Text;10 11using Microsoft.PowerShell.Commands.Internal.Format;12 13namespace Microsoft.PowerShell.Commands.Internal.Format14{15    internal class TableWriter16    {17        /// <summary>18        /// Information about each column boundaries.19        /// </summary>20        private sealed class ColumnInfo21        {22            internal int startCol = 0;23            internal int width = 0;24            internal int alignment = TextAlignment.Left;25            internal bool HeaderMatchesProperty = true;26        }27        /// <summary>28        /// Class containing information about the tabular layout.29        /// </summary>30        private sealed class ScreenInfo31        {32            internal int screenColumns = 0;33            internal int screenRows = 0;34 35            internal const int separatorCharacterCount = 1;36 37            internal const int minimumScreenColumns = 5;38 39            internal const int minimumColumnWidth = 1;40 41            internal ColumnInfo[] columnInfo = null;42        }43 44        private ScreenInfo _si;45 46        private List<string> _header;47 48        internal static int ComputeWideViewBestItemsPerRowFit(int stringLen, int screenColumns)49        {50            if (stringLen <= 0 || screenColumns < 1)51                return 1;52 53            if (stringLen >= screenColumns)54            {55                // we too wide anyways, we might have to trim even for a single column56                return 1;57            }58 59            // we try to fit more than one: start increasing until we do not fit anymore60            int columnNumber = 1;61            while (true)62            {63                // would we fit with one more column?64                int nextValue = columnNumber + 1;65                // compute the width if we added the extra column66                int width = stringLen * nextValue + (nextValue - 1) * ScreenInfo.separatorCharacterCount;67 68                if (width >= screenColumns)69                {70                    // we would not fit, so we are done71                    return columnNumber;72                }73 74                // try another round75                columnNumber++;76            }77        }78 79        /// <summary>80        /// Initialize the table specifying the width of each column.81        /// </summary>82        /// <param name="leftMarginIndent">Left margin indentation.</param>83        /// <param name="screenColumns">Number of character columns on the screen.</param>84        /// <param name="columnWidths">Array of specified column widths.</param>85        /// <param name="alignment">Array of alignment flags.</param>86        /// <param name="headerMatchesProperty">Array of flags where the header label matches a property name.</param>87        /// <param name="suppressHeader">If true, suppress header printing.</param>88        /// <param name="screenRows">Number of rows on the screen.</param>89        internal void Initialize(int leftMarginIndent, int screenColumns, Span<int> columnWidths, ReadOnlySpan<int> alignment, ReadOnlySpan<bool> headerMatchesProperty, bool suppressHeader, int screenRows = int.MaxValue)90        {91            if (leftMarginIndent < 0)92            {93                leftMarginIndent = 0;94            }95 96            if (screenColumns - leftMarginIndent < ScreenInfo.minimumScreenColumns)97            {98                _disabled = true;99                return;100            }101 102            _startColumn = leftMarginIndent;103            _hideHeader = suppressHeader;104 105            // make sure the column widths are correct; if not, take appropriate action106            ColumnWidthManager manager = new ColumnWidthManager(107                screenColumns - leftMarginIndent,108                ScreenInfo.minimumColumnWidth,109                ScreenInfo.separatorCharacterCount);110 111            manager.CalculateColumnWidths(columnWidths);112 113            // if all the columns are hidden, just disable114            bool oneValid = false;115 116            for (int k = 0; k < columnWidths.Length; k++)117            {118                if (columnWidths[k] >= ScreenInfo.minimumColumnWidth)119                {120                    oneValid = true;121                    break;122                }123            }124 125            if (!oneValid)126            {127                _disabled = true;128                return;129            }130 131            // now set the run time data structures132            _si = new ScreenInfo();133            _si.screenColumns = screenColumns;134            _si.screenRows = screenRows;135            _si.columnInfo = new ColumnInfo[columnWidths.Length];136 137            int startCol = _startColumn;138            for (int k = 0; k < columnWidths.Length; k++)139            {140                _si.columnInfo[k] = new ColumnInfo();141                _si.columnInfo[k].startCol = startCol;142                _si.columnInfo[k].width = columnWidths[k];143                _si.columnInfo[k].alignment = alignment[k];144                if (!headerMatchesProperty.IsEmpty)145                {146                    _si.columnInfo[k].HeaderMatchesProperty = headerMatchesProperty[k];147                }148 149                startCol += columnWidths[k] + ScreenInfo.separatorCharacterCount;150            }151        }152 153        internal int GenerateHeader(string[] values, LineOutput lo)154        {155            if (_disabled || _hideHeader)156            {157                return 0;158            }159            else if (_header != null)160            {161                string style = PSStyle.Instance.Formatting.TableHeader;162                string reset = PSStyle.Instance.Reset;163 164                foreach (string line in _header)165                {166                    lo.WriteLine(line);167                }168 169                return _header.Count;170            }171 172            _header = new List<string>();173 174            // generate the row with the header labels175            GenerateRow(values, lo, true, null, lo.DisplayCells, _header, isHeader: true);176 177            // generate an array of "--" as header markers below178            // the column header labels179            string[] breakLine = new string[values.Length];180            for (int k = 0; k < breakLine.Length; k++)181            {182                // the column can be hidden183                if (_si.columnInfo[k].width <= 0)184                {185                    breakLine[k] = string.Empty;186                    continue;187                }188                // the title can be larger than the width189                int count = _si.columnInfo[k].width;190                if (!string.IsNullOrEmpty(values[k]))191                {192                    int labelDisplayCells = lo.DisplayCells.Length(values[k]);193                    if (labelDisplayCells < count)194                        count = labelDisplayCells;195                }196                // NOTE: we can do this because "-" is a single cell character197                // on all devices. If changed to some other character, this assumption198                // would be invalidated199                breakLine[k] = StringUtil.DashPadding(count);200            }201 202            GenerateRow(breakLine, lo, false, null, lo.DisplayCells, _header, isHeader: true);203            return _header.Count;204        }205 206        internal void GenerateRow(string[] values, LineOutput lo, bool multiLine, ReadOnlySpan<int> alignment, DisplayCells dc, List<string> generatedRows, bool isHeader = false)207        {208            if (_disabled)209            {210                return;211            }212 213            // build the current row alignment settings214            int cols = _si.columnInfo.Length;215            Span<int> currentAlignment = cols <= OutCommandInner.StackAllocThreshold ? stackalloc int[cols] : new int[cols];216 217            if (alignment.IsEmpty)218            {219                for (int i = 0; i < currentAlignment.Length; i++)220                {221                    currentAlignment[i] = _si.columnInfo[i].alignment;222                }223            }224            else225            {226                for (int i = 0; i < currentAlignment.Length; i++)227                {228                    if (alignment[i] == TextAlignment.Undefined)229                    {230                        currentAlignment[i] = _si.columnInfo[i].alignment;231                    }232                    else233                    {234                        currentAlignment[i] = alignment[i];235                    }236                }237            }238 239            string style = PSStyle.Instance.Formatting.TableHeader;240            string reset = PSStyle.Instance.Reset;241 242            if (multiLine)243            {244                foreach (string line in GenerateTableRow(values, currentAlignment, lo.DisplayCells, isHeader))245                {246                    generatedRows?.Add(line);247                    lo.WriteLine(line);248                }249            }250            else251            {252                string line = GenerateRow(values, currentAlignment, dc, isHeader);253                generatedRows?.Add(line);254                lo.WriteLine(line);255            }256        }257 258        private string[] GenerateTableRow(string[] values, ReadOnlySpan<int> alignment, DisplayCells ds, bool isHeader)259        {260            // select the active columns (skip hidden ones)261            Span<int> validColumnArray = _si.columnInfo.Length <= OutCommandInner.StackAllocThreshold ? stackalloc int[_si.columnInfo.Length] : new int[_si.columnInfo.Length];262            int validColumnCount = 0;263            for (int k = 0; k < _si.columnInfo.Length; k++)264            {265                if (_si.columnInfo[k].width > 0)266                {267                    validColumnArray[validColumnCount++] = k;268                }269            }270 271            if (validColumnCount == 0)272            {273                return null;274            }275 276            StringCollection[] scArray = new StringCollection[validColumnCount];277            bool addPadding = true;278            for (int k = 0; k < scArray.Length; k++)279            {280                // for the last column, don't pad it with trailing spaces281                if (k == scArray.Length - 1)282                {283                    addPadding = false;284                }285 286                // obtain a set of tokens for each field287                scArray[k] = GenerateMultiLineRowField(values[validColumnArray[k]], validColumnArray[k], alignment[validColumnArray[k]], ds, addPadding);288 289                // NOTE: the following padding operations assume that we290                // pad with a blank (or any character that ALWAYS maps to a single screen cell291                if (k > 0)292                {293                    // skipping the first ones, add a separator for concatenation294                    for (int j = 0; j < scArray[k].Count; j++)295                    {296                        scArray[k][j] = StringUtil.Padding(ScreenInfo.separatorCharacterCount) + scArray[k][j];297                    }298                }299                else300                {301                    // add indentation padding if needed302                    if (_startColumn > 0)303                    {304                        for (int j = 0; j < scArray[k].Count; j++)305                        {306                            scArray[k][j] = StringUtil.Padding(_startColumn) + scArray[k][j];307                        }308                    }309                }310            }311 312            // now we processed all the rows columns and we need to find the cell that occupies the most313            // rows314            int screenRows = 0;315            for (int k = 0; k < scArray.Length; k++)316            {317                if (scArray[k].Count > screenRows)318                {319                    screenRows = scArray[k].Count;320                }321            }322 323            // column headers can span multiple rows if the width of the column is shorter than the header text like:324            //325            // Long Header2 Head326            // Head         er3327            // er328            // ---- ------- ----329            // 1    2       3330            //331            // To ensure we don't add whitespace to the end, we need to determine the last column in each row with content332            System.Span<int> lastColWithContent = screenRows <= OutCommandInner.StackAllocThreshold ? stackalloc int[screenRows] : new int[screenRows];333            for (int row = 0; row < screenRows; row++)334            {335                for (int col = 0; col < scArray.Length; col++)336                {337                    if (scArray[col].Count > row)338                    {339                        lastColWithContent[row] = col;340                    }341                }342            }343 344            // add padding for the columns that are shorter345            for (int col = 0; col < scArray.Length; col++)346            {347                int paddingBlanks = 0;348 349                // don't pad if last column350                if (col < scArray.Length - 1)351                {352                    paddingBlanks = _si.columnInfo[validColumnArray[col]].width;353                    if (col > 0)354                    {355                        paddingBlanks += ScreenInfo.separatorCharacterCount;356                    }357                    else358                    {359                        paddingBlanks += _startColumn;360                    }361                }362 363                int paddingEntries = screenRows - scArray[col].Count;364                if (paddingEntries > 0)365                {366                    for (int row = screenRows - paddingEntries; row < screenRows; row++)367                    {368                        // if the column is beyond the last column with content, just use empty string369                        if (col > lastColWithContent[row])370                        {371                            scArray[col].Add(string.Empty);372                        }373                        else374                        {375                            scArray[col].Add(StringUtil.Padding(paddingBlanks));376                        }377                    }378                }379            }380 381            // finally, build an array of strings382            string[] rows = new string[screenRows];383            for (int row = 0; row < screenRows; row++)384            {385                StringBuilder sb = new StringBuilder();386 387                // for a given row, walk the columns388                for (int col = 0; col < scArray.Length; col++)389                {390                    string value = scArray[col][row];391 392                    // if the column is the last column with content, we need to trim trailing whitespace, unless there is only one row393                    if (col == lastColWithContent[row] && screenRows > 1)394                    {395                        value = value.TrimEnd();396                    }397 398                    if (isHeader)399                    {400                        if (_si.columnInfo[col].HeaderMatchesProperty)401                        {402                            sb.Append(PSStyle.Instance.Formatting.TableHeader);403                        }404                        else if (value.Length > 0)405                        {406                            // after the first column, each additional column starts with a whitespace for separation407                            value = value.Insert(col == 0 ? 0 : 1, PSStyle.Instance.Formatting.CustomTableHeaderLabel);408                        }409                    }410 411                    sb.Append(value);412 413                    if (isHeader)414                    {415                        sb.Append(PSStyle.Instance.Reset);416                    }417                }418 419                rows[row] = sb.ToString();420            }421 422            return rows;423        }424 425        private StringCollection GenerateMultiLineRowField(string val, int k, int alignment, DisplayCells dc, bool addPadding)426        {427            StringCollection sc = StringManipulationHelper.GenerateLines(dc, val,428                                        _si.columnInfo[k].width, _si.columnInfo[k].width);429            if (addPadding || alignment == TextAlignment.Right || alignment == TextAlignment.Center)430            {431                // if length is shorter, do some padding432                for (int col = 0; col < sc.Count; col++)433                {434                    if (dc.Length(sc[col]) < _si.columnInfo[k].width)435                        sc[col] = GenerateRowField(sc[col], _si.columnInfo[k].width, alignment, dc, addPadding);436                }437            }438 439            return sc;440        }441 442        private string GenerateRow(string[] values, ReadOnlySpan<int> alignment, DisplayCells dc, bool isHeader)443        {444            StringBuilder sb = new StringBuilder();445 446            bool addPadding = true;447            for (int k = 0; k < _si.columnInfo.Length; k++)448            {449                // don't pad the last column450                if (k == _si.columnInfo.Length - 1)451                {452                    addPadding = false;453                }454 455                if (_si.columnInfo[k].width <= 0)456                {457                    // skip columns that are not at least a single character wide458                    continue;459                }460 461                // NOTE: the following padding operations assume that we462                // pad with a blank (or any character that ALWAYS maps to a single screen cell463                if (k > 0)464                {465                    sb.Append(StringUtil.Padding(ScreenInfo.separatorCharacterCount));466                }467                else468                {469                    // add indentation padding if needed470                    if (_startColumn > 0)471                    {472                        sb.Append(StringUtil.Padding(_startColumn));473                    }474                }475 476                string rowField = GenerateRowField(values[k], _si.columnInfo[k].width, alignment[k], dc, addPadding);477                if (isHeader)478                {479                    sb.Append(PSStyle.Instance.Formatting.TableHeader);480                }481 482                sb.Append(rowField);483 484                if (isHeader || (rowField is not null && rowField.Contains(ValueStringDecorated.ESC) && !rowField.AsSpan().TrimEnd().EndsWith(PSStyle.Instance.Reset)))485                {486                    // Reset the console output if the content of this column contains ESC487                    sb.Append(PSStyle.Instance.Reset);488                }489            }490 491            return sb.ToString();492        }493 494        private static string GenerateRowField(string val, int width, int alignment, DisplayCells dc, bool addPadding)495        {496            // make sure the string does not have any embedded <CR> in it497            string s = StringManipulationHelper.TruncateAtNewLine(val);498            int currentValueDisplayLength = dc.Length(s);499 500            if (currentValueDisplayLength < width)501            {502                // the string is shorter than the width of the column503                // need to pad with blanks to reach the desired width504                int padCount = width - currentValueDisplayLength;505                switch (alignment)506                {507                    case TextAlignment.Right:508                        {509                            s = StringUtil.Padding(padCount) + s;510                        }511 512                        break;513 514                    case TextAlignment.Center:515                        {516                            // add a bit at both ends of the string517                            int padLeft = padCount / 2;518                            int padRight = padCount - padLeft;519 520                            s = StringUtil.Padding(padLeft) + s;521                            if (addPadding)522                            {523                                s += StringUtil.Padding(padRight);524                            }525                        }526 527                        break;528 529                    default:530                        {531                            if (addPadding)532                            {533                                // left align is the default534                                s += StringUtil.Padding(padCount);535                            }536                        }537 538                        break;539                }540            }541            else if (currentValueDisplayLength > width)542            {543                // the string is longer than the width of the column544                // truncate and add ellipsis if it's too long545                int truncationDisplayLength = width - EllipsisSize;546 547                if (truncationDisplayLength > 0)548                {549                    // we have space for the ellipsis, add it550                    switch (alignment)551                    {552                        case TextAlignment.Right:553                            {554                                // get from "abcdef" to "...f"555                                s = s.VtSubstring(556                                    startOffset: dc.TruncateHead(s, truncationDisplayLength),557                                    prependStr: PSObjectHelper.EllipsisStr,558                                    appendStr: null);559                            }560 561                            break;562 563                        default:564                            {565                                // left align is the default566                                // get from "abcdef" to "a..."567                                s = s.VtSubstring(568                                    startOffset: 0,569                                    length: dc.TruncateTail(s, truncationDisplayLength),570                                    prependStr: null,571                                    appendStr: PSObjectHelper.EllipsisStr);572                            }573 574                            break;575                    }576                }577                else578                {579                    // not enough space for the ellipsis, just truncate at the width580                    switch (alignment)581                    {582                        case TextAlignment.Right:583                            {584                                // get from "abcdef" to "f"585                                s = s.VtSubstring(startOffset: dc.TruncateHead(s, width));586                            }587 588                            break;589 590                        default:591                            {592                                // left align is the default593                                // get from "abcdef" to "a"594                                s = s.VtSubstring(startOffset: 0, length: dc.TruncateTail(s, width));595                            }596 597                            break;598                    }599                }600            }601 602            // we need to take into consideration that truncation left the string one603            // display cell short if a double cell character got truncated604            // in this case, we need to pad with a blank605            int finalValueDisplayLength = dc.Length(s);606            if (finalValueDisplayLength == width)607            {608                return s;609            }610 611            switch (alignment)612            {613                case TextAlignment.Right:614                    {615                        s = " " + s;616                    }617 618                    break;619 620                case TextAlignment.Center:621                    {622                        if (addPadding)623                        {624                            s += " ";625                        }626                    }627 628                    break;629 630                default:631                    {632                        // left align is the default633                        if (addPadding)634                        {635                            s += " ";636                        }637                    }638 639                    break;640            }641 642            return s;643        }644 645        private const int EllipsisSize = 1;646 647        private bool _disabled = false;648 649        private bool _hideHeader = false;650 651        private int _startColumn = 0;652    }653}654