winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

TableOutput.h (6854B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #pragma once
      4 #include "ExecutionReporter.h"
      5 #include "Resources.h"
      6 
      7 #include <array>
      8 #include <ostream>
      9 #include <string>
     10 #include <vector>
     11 
     12 
     13 namespace AppInstaller::CLI::Execution
     14 {
     15     // Enables output data in a table format.
     16     // TODO: Improve for use with sparse data.
     17     template <size_t FieldCount>
     18     struct TableOutput
     19     {
     20         using header_t = std::array<Resource::LocString, FieldCount>;
     21         using line_t = std::array<std::string, FieldCount>;
     22 
     23         TableOutput(Reporter& reporter, header_t&& header, size_t sizingBuffer = 50) :
     24             m_reporter(reporter), m_sizingBuffer(sizingBuffer)
     25         {
     26             for (size_t i = 0; i < FieldCount; ++i)
     27             {
     28                 m_columns[i].Name = std::move(header[i]);
     29                 m_columns[i].MinLength = Utility::UTF8ColumnWidth(m_columns[i].Name.get());
     30                 m_columns[i].MaxLength = 0;
     31             }
     32         }
     33 
     34         void OutputLine(line_t&& line)
     35         {
     36             m_empty = false;
     37 
     38             if (m_buffer.size() < m_sizingBuffer)
     39             {
     40                 m_buffer.emplace_back(std::move(line));
     41             }
     42             else
     43             {
     44                 EvaluateAndFlushBuffer();
     45                 OutputLineToStream(line);
     46             }
     47         }
     48 
     49         void Complete()
     50         {
     51             if (!m_empty)
     52             {
     53                 EvaluateAndFlushBuffer();
     54             }
     55         }
     56 
     57         bool IsEmpty()
     58         {
     59             return m_empty;
     60         }
     61 
     62     private:
     63         // A column in the table.
     64         struct Column
     65         {
     66             Resource::LocString Name;
     67             size_t MinLength = 0;
     68             size_t MaxLength = 0;
     69             bool SpaceAfter = true;
     70         };
     71 
     72         Reporter& m_reporter;
     73         std::array<Column, FieldCount> m_columns;
     74         size_t m_sizingBuffer;
     75         std::vector<line_t> m_buffer;
     76         bool m_bufferEvaluated = false;
     77         bool m_empty = true;
     78 
     79         void EvaluateAndFlushBuffer()
     80         {
     81             if (m_bufferEvaluated)
     82             {
     83                 return;
     84             }
     85 
     86             // Determine the maximum length for all columns
     87             for (const auto& line : m_buffer)
     88             {
     89                 for (size_t i = 0; i < FieldCount; ++i)
     90                 {
     91                     m_columns[i].MaxLength = std::max(m_columns[i].MaxLength, Utility::UTF8ColumnWidth(line[i]));
     92                 }
     93             }
     94 
     95             // If there are actually columns with data, then also bring in the minimum size
     96             for (size_t i = 0; i < FieldCount; ++i)
     97             {
     98                 if (m_columns[i].MaxLength)
     99                 {
    100                     m_columns[i].MaxLength = std::max(m_columns[i].MaxLength, m_columns[i].MinLength);
    101                 }
    102             }
    103 
    104             // Only output the extra space if:
    105             // 1. Not the last field
    106             m_columns[FieldCount - 1].SpaceAfter = false;
    107 
    108             // 2. Not empty (taken care of by not doing anything if empty)
    109             // 3. There are non-empty fields after
    110             for (size_t i = FieldCount - 1; i > 0; --i)
    111             {
    112                 if (m_columns[i].MaxLength)
    113                 {
    114                     break;
    115                 }
    116                 else
    117                 {
    118                     m_columns[i - 1].SpaceAfter = false;
    119                 }
    120             }
    121 
    122             // Determine the total width required to not truncate any columns
    123             size_t totalRequired = 0;
    124 
    125             for (size_t i = 0; i < FieldCount; ++i)
    126             {
    127                 totalRequired += m_columns[i].MaxLength + (m_columns[i].SpaceAfter ? 1 : 0);
    128             }
    129 
    130             size_t consoleWidth = GetConsoleWidth();
    131 
    132             // If the total space would be too big, shrink them.
    133             // We don't want to use the last column, lest we auto-wrap
    134             if (totalRequired >= consoleWidth)
    135             {
    136                 size_t extra = (totalRequired - consoleWidth) + 1;
    137 
    138                 while (extra)
    139                 {
    140                     size_t targetIndex = 0;
    141                     size_t targetVal = m_columns[0].MaxLength;
    142                     for (size_t j = 1; j < FieldCount; ++j)
    143                     {
    144                         if (m_columns[j].MaxLength > targetVal)
    145                         {
    146                             targetIndex = j;
    147                             targetVal = m_columns[j].MaxLength;
    148                         }
    149                     }
    150                     m_columns[targetIndex].MaxLength -= 1;
    151                     extra -= 1;
    152                 }
    153 
    154                 totalRequired = consoleWidth - 1;
    155             }
    156 
    157             // Header line
    158             line_t headerLine;
    159 
    160             for (size_t i = 0; i < FieldCount; ++i)
    161             {
    162                 headerLine[i] = m_columns[i].Name.get();
    163             }
    164 
    165             OutputLineToStream(headerLine);
    166 
    167             m_reporter.Info() << std::string(totalRequired, '-') << std::endl;
    168 
    169             for (const auto& line : m_buffer)
    170             {
    171                 OutputLineToStream(line);
    172             }
    173 
    174             m_bufferEvaluated = true;
    175         }
    176 
    177         void OutputLineToStream(const line_t& line)
    178         {
    179             auto out = m_reporter.Info();
    180 
    181             for (size_t i = 0; i < FieldCount; ++i)
    182             {
    183                 const auto& col = m_columns[i];
    184 
    185                 if (col.MaxLength)
    186                 {
    187                     size_t valueLength = Utility::UTF8ColumnWidth(line[i]);
    188 
    189                     if (valueLength > col.MaxLength)
    190                     {
    191                         size_t actualWidth;
    192                         out << Utility::UTF8TrimRightToColumnWidth(line[i], col.MaxLength - 1, actualWidth) << "\xE2\x80\xA6"; // UTF8 encoding of ellipsis (…) character
    193 
    194                         // Some characters take 2 unit space, the trimmed string length might be 1 less than the expected length.
    195                         if (actualWidth != col.MaxLength - 1)
    196                         {
    197                             out << ' ';
    198                         }
    199 
    200                         if (col.SpaceAfter)
    201                         {
    202                             out << ' ';
    203                         }
    204                     }
    205                     else
    206                     {
    207                         out << line[i];
    208 
    209                         if (col.SpaceAfter)
    210                         {
    211                             out << std::string(col.MaxLength - valueLength + 1, ' ');
    212                         }
    213                     }
    214                 }
    215             }
    216 
    217             out << std::endl;
    218         }
    219     };
    220 }