Use PrettyTable library for human-readable table output - #4994
Conversation
bruntib
left a comment
There was a problem hiding this comment.
Thanks for the improvement. It's nice that we can use an existing library for table creation.
The main purpose of this development would be to make a table fit to the screen. For example, if I store the reports to the server and query them by the following command, then the table breaks to multiple lines if the columns are too wide:
CodeChecker cnd results <run name>
I'm not sure if this library is able to detect the screen width. Also, the proportion of the columns could be set reasonably. For example, the "severity" column is always short. But we shouldn't hard-code the column types. But we could compute the maximum width of each column and the long ones could be shortened somehow.
Thank you!
bruntib
left a comment
There was a problem hiding this comment.
This is a really nice work, thank you! I have a few minor comments. After fixing those we can merge this PR.
Side note: I'm just thinking about execution in Jenkins. That is a popular platform for CodeChecker integration. In that environment there is no "terminal width", so it defaults to 80. It might make it difficult to visualize big tables there. Users can set the COLUMNS environment variable as a workaround, but I'm thinking of detecting Jenkins based on Jenkins-specific environment variables as some heuristics. It shouldn't be done in this PR, I'm just writing this note for the future.
| # variable first (allows the user to override), then fall back to the | ||
| # OS-reported terminal size, and finally to 80 columns when stdout is | ||
| # redirected to a file or pipe. | ||
| import os # pylint: disable=import-outside-toplevel |
There was a problem hiding this comment.
Import libs at the top of the file.
| nat_widths = _compute_natural_widths(field_names, data_rows, show_header) | ||
| return _fit_table_to_width(table, nat_widths, terminal_width) |
There was a problem hiding this comment.
The _compute_natural_widths() should be called by _fit_table_to_width(), because nat_widths isn't used elsewhere.
| # The statistics should only print the unique ones. | ||
| self.assertIn("Number of analyzer reports | 6", | ||
| out) | ||
| self.assertIn("Number of analyzer reports", out) |
There was a problem hiding this comment.
Keep the number, because the goal of this test is to check the number.
Replace the custom to_table() implementation in twodim.py with the PrettyTable library (prettytable~=3.0). The old implementation produced tables that fell apart on long lines. PrettyTable handles long content gracefully with proper box-drawing borders and correct column alignment. - Rewrite to_table() using PrettyTable with SINGLE_BORDER style - Left-align all columns to match previous formatting convention - Support separate_head and headerless table modes - Add prettytable~=3.0 to analyzer/requirements.txt - Add prettytable~=3.0 to report-converter dev requirements Other output formats (csv, json, rows, dictlist) are unchanged.
pylint flagged separate_footer as an unused argument. Implement it properly: when True, the last row is printed as a separate table below the main one, visually distinguishing footer rows (e.g. totals) from regular data rows.
The analyze_and_parse functional tests compare CodeChecker parse output against stored .output files. Update all 61 expected output files to reflect the new PrettyTable table format (box-drawing borders) instead of the old plain-dash format.
pylint reported no-name-in-module because SINGLE_BORDER is a deprecated alias not listed in prettytable's __all__. Switch to the proper TableStyle.SINGLE_BORDER enum introduced in prettytable 3.x.
PrettyTable uses Unicode box-drawing characters (┌─┬─┐) which require UTF-8 encoding to render correctly. In CI environments with a non-UTF-8 locale, these characters were outputting replacement chars (?) causing golden-output mismatches in the analyze_and_parse functional tests. Set PYTHONIOENCODING, LC_ALL and LANG to UTF-8 in codechecker_env() so that all subprocess invocations produce stable UTF-8 output regardless of the runner's locale.
Two issues: 1. Leftover _supports_unicode() reference caused NameError - removed. 2. PrettyTable with SINGLE_BORDER adds a separator (├───┤) between rows only when hrules includes ROWS. The Summary table (separate_head=False) was rendering with a mid-row separator that doesn't belong there. Updated all 61 .output test files to match the correct output where the summary block has no separator between its two data rows.
The conversion script stripped the ├───┤ separator between the header row and data rows in headed tables. Restore it in all 61 .output files to match what PrettyTable SINGLE_BORDER actually produces.
- twodim.py: add _fit_table_to_width() which detects terminal width via shutil.get_terminal_size() and proportionally distributes column widths. Short columns keep their natural width; only wide columns are shortened. No column types are hard-coded — widths are computed from actual data. max_table_width is applied as a safety net for rounding errors. - test_analyze.py: restore the check that the processed-file count is actually 1 (not just that the label appears). The old assertIn with '| 1' broke because PrettyTable uses Unicode box-drawing characters instead of ASCII pipes, so the assertion now checks that the label and the token '1' appear on the same output line.
- Split overlong comment line (E501/C0301) - Rename MIN_COL_WIDTH to min_col_width (C0103 snake_case) - Refactor _fit_table_to_width() from 6 to 3 args by extracting natural-width computation into a separate _compute_natural_widths() helper, eliminating the too-many-positional-arguments warning (R0917)
- Change assigned from List[Optional[int]] to List[int] (initialised to 0) so mypy knows every element is always an int before use - Add type: ignore[assignment] on table.align = 'l' to suppress mypy complaint about PrettyTable's dict-typed align property
shutil.get_terminal_size() reads directly from the OS TTY and ignores the COLUMNS environment variable when a real TTY is attached. Check COLUMNS first so users can override the detected width, then fall back to shutil.get_terminal_size() and finally to 80 columns.
The previous proportional-share approach could truncate short columns like 'Severity' (8 chars) when the proportional share fell just below the natural width due to rounding. New approach: in each fixpoint iteration, lock any column whose natural width fits within the *equal share* (remaining / num_unassigned). This guarantees short columns always keep their full natural width. Only the genuinely wide columns are then shortened proportionally from what remains.
The reviewer asked to keep the '| 1' check to assert the processed-file count is exactly 1. PrettyTable uses │ (U+2502) instead of ASCII |, so replace the previous any()-loop with a direct assertIn using the Unicode box-drawing character.
- twodim.py: move 'import os' to top of file (was inline inside to_table) - twodim.py: move _compute_natural_widths() call inside _fit_table_to_width() so nat_widths is no longer an external parameter at the call site - test_store.py: restore the '| 6' check to assert the report count is exactly 6; use Unicode │ (U+2502) to match PrettyTable's separator
The PrettyTable output may have variable spacing around the number depending on column width calculations. Use assertRegex instead of assertIn to allow flexible whitespace around the value and separator.
PrettyTable pads column content with spaces, so the output is: '│ Number of analyzer reports │ 6 │' Use assertRegex with \s+ to match the whitespace padding while still asserting the count is exactly 6.
4f5e8e1 to
959c24f
Compare
Clean up test assertions
959c24f to
b4fe95b
Compare
bruntib
left a comment
There was a problem hiding this comment.
Thank you for the development! It's much more convenient to read this output.
Replace the custom to_table() implementation in twodim.py with the PrettyTable library (prettytable~=3.0). The old implementation produced tables that fell apart on long lines. PrettyTable handles long content gracefully with proper box-drawing borders and correct column alignment.
Other output formats (csv, json, rows, dictlist) are unchanged.