1 // Copyright 2012 The Kyua Authors. 2 // All rights reserved. 3 // 4 // Redistribution and use in source and binary forms, with or without 5 // modification, are permitted provided that the following conditions are 6 // met: 7 // 8 // * Redistributions of source code must retain the above copyright 9 // notice, this list of conditions and the following disclaimer. 10 // * Redistributions in binary form must reproduce the above copyright 11 // notice, this list of conditions and the following disclaimer in the 12 // documentation and/or other materials provided with the distribution. 13 // * Neither the name of Google Inc. nor the names of its contributors 14 // may be used to endorse or promote products derived from this software 15 // without specific prior written permission. 16 // 17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29 #include "utils/text/table.hpp" 30 31 #include <algorithm> 32 #include <iterator> 33 #include <limits> 34 #include <sstream> 35 36 #include "utils/sanity.hpp" 37 #include "utils/text/operations.ipp" 38 39 namespace text = utils::text; 40 41 42 namespace { 43 44 45 /// Applies user overrides to the column widths of a table. 46 /// 47 /// \param table The table from which to calculate the column widths. 48 /// \param user_widths The column widths provided by the user. This vector must 49 /// have less or the same number of elements as the columns of the table. 50 /// Values of width_auto are ignored; any other explicit values are copied 51 /// to the output widths vector, including width_refill. 52 /// 53 /// \return A vector with the widths of the columns of the input table with any 54 /// user overrides applied. 55 static text::widths_vector 56 override_column_widths(const text::table& table, 57 const text::widths_vector& user_widths) 58 { 59 PRE(user_widths.size() <= table.ncolumns()); 60 text::widths_vector widths = table.column_widths(); 61 62 // Override the actual width of the columns based on user-specified widths. 63 for (text::widths_vector::size_type i = 0; i < user_widths.size(); ++i) { 64 const text::widths_vector::value_type& user_width = user_widths[i]; 65 if (user_width != text::table_formatter::width_auto) { 66 PRE_MSG(user_width == text::table_formatter::width_refill || 67 user_width >= widths[i], 68 "User-provided column widths must be larger than the " 69 "column contents (except for the width_refill column)"); 70 widths[i] = user_width; 71 } 72 } 73 74 return widths; 75 } 76 77 78 /// Locates the refill column, if any. 79 /// 80 /// \param widths The widths of the columns as returned by 81 /// override_column_widths(). Note that one of the columns may or may not 82 /// be width_refill, which is the column we are looking for. 83 /// 84 /// \return The index of the refill column with a width_refill width if any, or 85 /// otherwise the index of the last column (which is the default refill column). 86 static text::widths_vector::size_type 87 find_refill_column(const text::widths_vector& widths) 88 { 89 text::widths_vector::size_type i = 0; 90 for (; i < widths.size(); ++i) { 91 if (widths[i] == text::table_formatter::width_refill) 92 return i; 93 } 94 return i - 1; 95 } 96 97 98 /// Pads the widths of the table to fit within a maximum width. 99 /// 100 /// On output, a column of the widths vector is truncated to a shorter length 101 /// than its current value, if the total width of the table would exceed the 102 /// maximum table width. 103 /// 104 /// \param [in,out] widths The widths of the columns as returned by 105 /// override_column_widths(). One of these columns should have a value of 106 /// width_refill; if not, a default column is refilled. 107 /// \param user_max_width The target width of the table; must not be zero. 108 /// \param column_padding The padding between the cells, if any. The target 109 /// width should be larger than the padding times the number of columns; if 110 /// that is not the case, we attempt a readjustment here. 111 static void 112 refill_widths(text::widths_vector& widths, 113 const text::widths_vector::value_type user_max_width, 114 const std::size_t column_padding) 115 { 116 PRE(user_max_width != 0); 117 118 // widths.size() is a proxy for the number of columns of the table. 119 const std::size_t total_padding = column_padding * (widths.size() - 1); 120 const text::widths_vector::value_type max_width = std::max( 121 user_max_width, total_padding) - total_padding; 122 123 const text::widths_vector::size_type refill_column = 124 find_refill_column(widths); 125 INV(refill_column < widths.size()); 126 127 text::widths_vector::value_type width = 0; 128 for (text::widths_vector::size_type i = 0; i < widths.size(); ++i) { 129 if (i != refill_column) 130 width += widths[i]; 131 } 132 widths[refill_column] = max_width - width; 133 } 134 135 136 /// Pads an input text to a specified width with spaces. 137 /// 138 /// \param input The text to add padding to (may be empty). 139 /// \param length The desired length of the output. 140 /// \param is_last Whether the text being processed belongs to the last column 141 /// of a row or not. Values in the last column should not be padded to 142 /// prevent trailing whitespace on the screen (which affects copy/pasting 143 /// for example). 144 /// 145 /// \return The padded cell. If the input string is longer than the desired 146 /// length, the input string is returned verbatim. The padded table won't be 147 /// correct, but we don't expect this to be a common case to worry about. 148 static std::string 149 pad_cell(const std::string& input, const std::size_t length, const bool is_last) 150 { 151 if (is_last) 152 return input; 153 else { 154 if (input.length() < length) 155 return input + std::string(length - input.length(), ' '); 156 else 157 return input; 158 } 159 } 160 161 162 /// Refills a cell and adds it to the output lines. 163 /// 164 /// \param row The row containing the cell to be refilled. 165 /// \param widths The widths of the row. 166 /// \param column The column being refilled. 167 /// \param [in,out] textual_rows The output lines as processed so far. This is 168 /// updated to accomodate for the contents of the refilled cell, extending 169 /// the rows as necessary. 170 static void 171 refill_cell(const text::table_row& row, const text::widths_vector& widths, 172 const text::table_row::size_type column, 173 std::vector< text::table_row >& textual_rows) 174 { 175 const std::vector< std::string > rows = text::refill(row[column], 176 widths[column]); 177 178 if (textual_rows.size() < rows.size()) 179 textual_rows.resize(rows.size(), text::table_row(row.size())); 180 181 for (std::vector< std::string >::size_type i = 0; i < rows.size(); ++i) { 182 for (text::table_row::size_type j = 0; j < row.size(); ++j) { 183 const bool is_last = j == row.size() - 1; 184 if (j == column) 185 textual_rows[i][j] = pad_cell(rows[i], widths[j], is_last); 186 else { 187 if (textual_rows[i][j].empty()) 188 textual_rows[i][j] = pad_cell("", widths[j], is_last); 189 } 190 } 191 } 192 } 193 194 195 /// Formats a single table row. 196 /// 197 /// \param row The row to format. 198 /// \param widths The widths of the columns to apply during formatting. Cells 199 /// wider than the specified width are refilled to attempt to fit in the 200 /// cell. Cells narrower than the width are right-padded with spaces. 201 /// \param separator The column separator to use. 202 /// 203 /// \return The textual lines that contain the formatted row. 204 static std::vector< std::string > 205 format_row(const text::table_row& row, const text::widths_vector& widths, 206 const std::string& separator) 207 { 208 PRE(row.size() == widths.size()); 209 210 std::vector< text::table_row > textual_rows(1, text::table_row(row.size())); 211 212 for (text::table_row::size_type column = 0; column < row.size(); ++column) { 213 if (widths[column] > row[column].length()) 214 textual_rows[0][column] = pad_cell(row[column], widths[column], 215 column == row.size() - 1); 216 else 217 refill_cell(row, widths, column, textual_rows); 218 } 219 220 std::vector< std::string > lines; 221 for (std::vector< text::table_row >::const_iterator 222 iter = textual_rows.begin(); iter != textual_rows.end(); ++iter) { 223 lines.push_back(text::join(*iter, separator)); 224 } 225 return lines; 226 } 227 228 229 } // anonymous namespace 230 231 232 /// Constructs a new table. 233 /// 234 /// \param ncolumns_ The number of columns that the table will have. 235 text::table::table(const table_row::size_type ncolumns_) 236 { 237 _column_widths.resize(ncolumns_, 0); 238 } 239 240 241 /// Gets the number of columns in the table. 242 /// 243 /// \return The number of columns in the table. This value remains constant 244 /// during the existence of the table. 245 text::widths_vector::size_type 246 text::table::ncolumns(void) const 247 { 248 return _column_widths.size(); 249 } 250 251 252 /// Gets the width of a column. 253 /// 254 /// The returned value is not valid if add_row() is called again, as the column 255 /// may have grown in width. 256 /// 257 /// \param column The index of the column of which to get the width. Must be 258 /// less than the total number of columns. 259 /// 260 /// \return The width of a column. 261 text::widths_vector::value_type 262 text::table::column_width(const widths_vector::size_type column) const 263 { 264 PRE(column < _column_widths.size()); 265 return _column_widths[column]; 266 } 267 268 269 /// Gets the widths of all columns. 270 /// 271 /// The returned value is not valid if add_row() is called again, as the columns 272 /// may have grown in width. 273 /// 274 /// \return A vector with the width of all columns. 275 const text::widths_vector& 276 text::table::column_widths(void) const 277 { 278 return _column_widths; 279 } 280 281 282 /// Checks whether the table is empty or not. 283 /// 284 /// \return True if the table is empty; false otherwise. 285 bool 286 text::table::empty(void) const 287 { 288 return _rows.empty(); 289 } 290 291 292 /// Adds a row to the table. 293 /// 294 /// \param row The row to be added. This row must have the same amount of 295 /// columns as defined during the construction of the table. 296 void 297 text::table::add_row(const table_row& row) 298 { 299 PRE(row.size() == _column_widths.size()); 300 _rows.push_back(row); 301 302 for (table_row::size_type i = 0; i < row.size(); ++i) 303 if (_column_widths[i] < row[i].length()) 304 _column_widths[i] = row[i].length(); 305 } 306 307 308 /// Gets an iterator pointing to the beginning of the rows of the table. 309 /// 310 /// \return An iterator on the rows. 311 text::table::const_iterator 312 text::table::begin(void) const 313 { 314 return _rows.begin(); 315 } 316 317 318 /// Gets an iterator pointing to the end of the rows of the table. 319 /// 320 /// \return An iterator on the rows. 321 text::table::const_iterator 322 text::table::end(void) const 323 { 324 return _rows.end(); 325 } 326 327 328 /// Column width to denote that the column has to fit all of its cells. 329 const std::size_t text::table_formatter::width_auto = 0; 330 331 332 /// Column width to denote that the column can be refilled to fit the table. 333 const std::size_t text::table_formatter::width_refill = 334 std::numeric_limits< std::size_t >::max(); 335 336 337 /// Constructs a new table formatter. 338 text::table_formatter::table_formatter(void) : 339 _separator(""), 340 _table_width(0) 341 { 342 } 343 344 345 /// Sets the width of a column. 346 /// 347 /// All columns except one must have a width that is, at least, as wide as the 348 /// widest cell in the column. One of the columns can have a width of 349 /// width_refill, which indicates that the column will be refilled if the table 350 /// does not fit in its maximum width. 351 /// 352 /// \param column The index of the column to set the width for. 353 /// \param width The width to set the column to. 354 /// 355 /// \return A reference to this formatter to allow using the builder pattern. 356 text::table_formatter& 357 text::table_formatter::set_column_width(const table_row::size_type column, 358 const std::size_t width) 359 { 360 #if !defined(NDEBUG) 361 if (width == width_refill) { 362 for (widths_vector::size_type i = 0; i < _column_widths.size(); i++) { 363 if (i != column) 364 PRE_MSG(_column_widths[i] != width_refill, 365 "Only one column width can be set to width_refill"); 366 } 367 } 368 #endif 369 370 if (_column_widths.size() < column + 1) 371 _column_widths.resize(column + 1, width_auto); 372 _column_widths[column] = width; 373 return *this; 374 } 375 376 377 /// Sets the separator to use between the cells. 378 /// 379 /// \param separator The separator to use. 380 /// 381 /// \return A reference to this formatter to allow using the builder pattern. 382 text::table_formatter& 383 text::table_formatter::set_separator(const char* separator) 384 { 385 _separator = separator; 386 return *this; 387 } 388 389 390 /// Sets the maximum width of the table. 391 /// 392 /// \param table_width The maximum width of the table; cannot be zero. 393 /// 394 /// \return A reference to this formatter to allow using the builder pattern. 395 text::table_formatter& 396 text::table_formatter::set_table_width(const std::size_t table_width) 397 { 398 PRE(table_width > 0); 399 _table_width = table_width; 400 return *this; 401 } 402 403 404 /// Formats a table into a collection of textual lines. 405 /// 406 /// \param t Table to format. 407 /// 408 /// \return A collection of textual lines. 409 std::vector< std::string > 410 text::table_formatter::format(const table& t) const 411 { 412 std::vector< std::string > lines; 413 414 if (!t.empty()) { 415 widths_vector widths = override_column_widths(t, _column_widths); 416 if (_table_width != 0) 417 refill_widths(widths, _table_width, _separator.length()); 418 419 for (table::const_iterator iter = t.begin(); iter != t.end(); ++iter) { 420 const std::vector< std::string > sublines = 421 format_row(*iter, widths, _separator); 422 std::copy(sublines.begin(), sublines.end(), 423 std::back_inserter(lines)); 424 } 425 } 426 427 return lines; 428 } 429