mconf: line wrap columns nicely

I've picked 160 characters as a default because anything less than that
looks terrible and has awful wrapping going on. However, this respects
the $COLUNNS environment variable if set, and otherwise will query the
terminal to determine the size.

This is all achieved through an application of shtuil.get_terminal_size,
textwrap, print formatters, and iteration.

Fixes #6965
pull/8680/head
Dylan Baker 4 years ago
parent cd8c334788
commit d3003ebb41
  1. 54
      mesonbuild/mconf.py

@ -12,7 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import itertools
import shutil
import os import os
import textwrap
import typing as T import typing as T
from . import build from . import build
@ -58,6 +61,7 @@ class Conf:
self.value_col = [] self.value_col = []
self.choices_col = [] self.choices_col = []
self.descr_col = [] self.descr_col = []
# XXX: is there a case where this can actually remain false?
self.has_choices = False self.has_choices = False
self.all_subprojects: T.Set[str] = set() self.all_subprojects: T.Set[str] = set()
self.yielding_options: T.Set[OptionKey] = set() self.yielding_options: T.Set[OptionKey] = set()
@ -97,16 +101,54 @@ class Conf:
# are erased when Meson is executed the next time, i.e. when # are erased when Meson is executed the next time, i.e. when
# Ninja is run. # Ninja is run.
def print_aligned(self): def print_aligned(self) -> None:
col_widths = (max([len(i) for i in self.name_col], default=0), """Do the actual printing.
max([len(i) for i in self.value_col], default=0),
max([len(i) for i in self.choices_col], default=0)) This prints the generated output in an aligned, pretty form. it aims
for a total width of 160 characters, but will use whatever the tty
reports it's value to be. Though this is much wider than the standard
80 characters of terminals, and even than the newer 120, compressing
it to those lengths makes the output hard to read.
Each column will have a specific width, and will be line wrapped.
"""
total_width = shutil.get_terminal_size(fallback=(160, 0))[0]
_col = max(total_width // 5, 20)
four_column = (_col, _col, _col, total_width - (3 * _col))
# In this case we don't have the choices field, so we can redistribute
# the extra 40 characters to val and desc
three_column = (_col, _col * 2, total_width // 2)
for line in zip(self.name_col, self.value_col, self.choices_col, self.descr_col): for line in zip(self.name_col, self.value_col, self.choices_col, self.descr_col):
if not any(line):
print('')
continue
# This is a header, like `Subproject foo:`,
# We just want to print that and get on with it
if line[0] and not any(line[1:]):
print(line[0])
continue
# wrap will take a long string, and create a list of strings no
# longer than the size given. Then that list can be zipped into, to
# print each line of the output, such the that columns are printed
# to the right width, row by row.
if self.has_choices: if self.has_choices:
print('{0:{width[0]}} {1:{width[1]}} {2:{width[2]}} {3}'.format(*line, width=col_widths)) name = textwrap.wrap(line[0], four_column[0])
val = textwrap.wrap(line[1], four_column[1])
choice = textwrap.wrap(line[2], four_column[2])
desc = textwrap.wrap(line[3], four_column[3])
for l in itertools.zip_longest(name, val, choice, desc, fillvalue=''):
# We must use the length modifier here to get even rows, as
# `textwrap.wrap` will only shorten, not lengthen each item
print('{:{widths[0]}} {:{widths[1]}} {:{widths[2]}} {}'.format(*l, widths=four_column))
else: else:
print('{0:{width[0]}} {1:{width[1]}} {3}'.format(*line, width=col_widths)) name = textwrap.wrap(line[0], three_column[0])
val = textwrap.wrap(line[1], three_column[1])
desc = textwrap.wrap(line[3], three_column[2])
for l in itertools.zip_longest(name, val, desc, fillvalue=''):
print('{:{widths[0]}} {:{widths[1]}} {}'.format(*l, widths=three_column))
def split_options_per_subproject(self, options: 'coredata.KeyedOptionDictType') -> T.Dict[str, T.Dict[str, 'UserOption']]: def split_options_per_subproject(self, options: 'coredata.KeyedOptionDictType') -> T.Dict[str, T.Dict[str, 'UserOption']]:
result: T.Dict[str, T.Dict[str, 'UserOption']] = {} result: T.Dict[str, T.Dict[str, 'UserOption']] = {}

Loading…
Cancel
Save