5 basf2.utils - Helper functions for printing basf2 objects
6 ---------------------------------------------------------
8 This modules contains some utility functions used by basf2, mainly for printing
12 import inspect
as _inspect
13 from shutil
import get_terminal_size
as _get_terminal_size
14 import textwrap
as _textwrap
18 def get_terminal_width():
20 Returns width of terminal in characters, or 80 if unknown.
22 return _get_terminal_size(fallback=(80, 24)).columns
25 def pretty_print_table(table, column_widths, first_row_is_heading=True, transform=None, min_flexible_width=10, *,
26 hline_formatter=None):
28 Pretty print a given table, by using available terminal size and
29 word wrapping fields as needed.
32 table: A 2d list of table fields. Each row must have the same length.
34 column_width: list of column widths, needs to be of same length as rows
35 in 'table'. Available fields are
38 as needed, up to n characters, word wrap if longer
41 as long as needed, no wrapping
47 use all available space, good for description fields.
48 If more than one column has a * they all get equal width
51 use all available space but at least the actual width. Only useful
52 to make the table span the full width of the terminal
54 The column width can also start with ``>``, ``<`` or ``^`` in which case
55 it will be right, left or center aligned.
57 first_row_is_heading: header specifies if we should take the first row
58 as table header and offset it a bit
60 transform: either None or a callback function which takes three
63 1. the elements of the row as a list
64 2. second the width of each column (without separator)
65 3. the preformatted text line.
67 It should return a string representing the final line to be
70 min_flexible_width: the minimum amount of characters for every column
73 hline_formatter: A callable function to format horizontal lines (above and
74 below the table header). Should be a callback with one parameter for
75 the total width of the table in characters and return a string that
76 is the horizontal line. If None is returned no line is printed.
78 If argument is not given or given as None the default of printing '-'
79 signs are printed over the whole table width is used.
81 .. versionchanged:: after release 5
82 Added support for column alignment
86 act_column_widths = [len(cell)
for cell
in table[0]]
88 for (col, cell)
in enumerate(row):
89 act_column_widths[col] = max(len(str(cell)), act_column_widths[col])
96 for (col, opt)
in enumerate(column_widths):
98 if isinstance(opt, str)
and opt[0]
in "<>^":
111 long_columns.append(col)
116 long_columns.append(- col - 1)
118 elif isinstance(opt, int)
and opt > 0:
120 act_column_widths[col] = opt
121 elif isinstance(opt, int)
and opt == 0:
124 elif isinstance(opt, int)
and opt < 0:
126 act_column_widths[col] = min(act_column_widths[col], -opt)
128 print(
'Invalid column_widths option "' + str(opt) +
'"')
130 total_used_width += act_column_widths[col]
133 total_used_width += len(act_column_widths) - 1
136 remaining_space = max(get_terminal_width() - total_used_width, len(long_columns) * min_flexible_width)
138 col_width, remainder = divmod(remaining_space, len(long_columns))
139 for i, col
in enumerate(long_columns):
140 size = col_width + (1
if i < remainder
else 0)
146 act_column_widths[col] = max(size, act_column_widths[col])
149 total_used_width += act_column_widths[col] - size
151 act_column_widths[col] = size
153 total_used_width += remaining_space
155 format_string =
' '.join([
'{:%s%d}' % opt
for opt
in zip(align, act_column_widths[:-1])])
158 format_string +=
' {}'
160 format_string +=
' {:%s%d}' % (align[-1], act_column_widths[-1])
162 if hline_formatter
is not None:
163 hline = hline_formatter(total_used_width)
165 hline = total_used_width *
"-"
168 if first_row_is_heading
and hline
is not None:
174 wrapped_row = [_textwrap.wrap(str(row[i]), width)
for (i, width)
in
175 enumerate(act_column_widths)]
176 max_lines = max([len(col)
for col
in wrapped_row])
177 for line
in range(max_lines):
178 for (i, cell)
in enumerate(row):
179 if line < len(wrapped_row[i]):
180 row[i] = wrapped_row[i][line]
183 line = format_string.format(*row)
184 if transform
is not None:
185 line = transform(row, act_column_widths, line)
188 if not header_shown
and first_row_is_heading
and hline
is not None:
193 def pretty_print_description_list(rows):
195 Given a list of 2-tuples, print a nicely formatted description list.
196 Rows with only one entry are interpreted as sub-headings
198 term_width = get_terminal_width()
202 wrapper = _textwrap.TextWrapper(width=term_width, initial_indent=
"",
203 subsequent_indent=
" " * (module_width))
205 useColors = pybasf2.LogPythonInterface.terminal_supports_colors()
208 """Use ANSI sequence to show string in bold"""
210 return '\x1b[1m' + text +
'\x1b[0m'
214 print(term_width *
'-')
220 print(bold(subheading).center(term_width))
222 name, description = row
223 for i, line
in enumerate(description.splitlines()):
228 wrapper.initial_indent = max(module_width, len(name) + 1) *
" "
229 print(bold(name.ljust(module_width - 1)), wrapper.fill(line).lstrip())
233 wrapper.initial_indent = wrapper.subsequent_indent
234 print(wrapper.fill(line))
236 print(term_width *
'-')
240 def print_all_modules(moduleList, package=''):
242 Loop over the list of available modules,
243 register them and print their information
249 for moduleName
in moduleList:
251 current_module = pybasf2._register_module(moduleName)
252 if package ==
'' or current_module.package() == package:
253 modules.append((current_module.package(), moduleName, current_module.description()))
254 except pybasf2.ModuleNotCreatedError:
255 pybasf2.B2ERROR(
'The module {} could not be loaded.'.format(moduleName))
257 except Exception
as e:
258 pybasf2.B2ERROR(
'An exception occurred when trying to load the module {}: {}'.format(moduleName, e))
263 for (packageName, moduleName, description)
in sorted(modules):
264 if current_package != packageName:
265 current_package = packageName
266 table.append((current_package,))
267 table.append((moduleName, description))
268 if package !=
'' and len(table) == 0:
269 pybasf2.B2FATAL(
'Print module information: No module or package named "' +
270 package +
'" found!')
272 pretty_print_description_list(table)
274 print(
'To show detailed information on a module, including its parameters,')
275 print(
"type \'basf2 -m ModuleName\'. Use \'basf2 -m package\' to only list")
276 print(
'modules belonging to a given package.')
279 pybasf2.B2FATAL(
"One or more modules could not be loaded. Please check the "
280 "following ERROR messages and contact the responsible authors.")
283 def print_params(module, print_values=True, shared_lib_path=None):
285 This function prints parameter information
288 module: Print the parameter information of this module
289 print_values: Set it to True to print the current values of the parameters
290 shared_lib_path: The path of the shared library from which the module was
295 print(
'=' * (len(module.name()) + 4))
296 print(
' %s' % module.name())
297 print(
'=' * (len(module.name()) + 4))
298 print(
'Description: %s' % module.description())
299 if shared_lib_path
is not None:
300 print(
'Found in: %s' % shared_lib_path)
301 print(
'Package: %s' % module.package())
314 output.append([
'Parameter',
'Type',
'Default',
'Description'])
316 has_forced_params =
False
317 paramList = module.available_params()
318 for paramItem
in paramList:
319 defaultStr = str(paramItem.default)
320 valueStr = str(paramItem.values)
322 if paramItem.forceInSteering:
324 has_forced_params =
True
328 forceString + paramItem.name,
332 paramItem.setInSteering,
333 paramItem.description])
335 output.append([forceString + paramItem.name, paramItem.type,
336 defaultStr, paramItem.description])
338 column_widths = [-25] * len(output[0])
339 column_widths[2] = -20
340 column_widths[-1] =
'*'
342 pretty_print_table(output, column_widths)
344 if has_forced_params:
345 print(
' * denotes a required parameter.')
348 def print_path(path, defaults=False, description=False, indentation=0, title=True):
350 This function prints the modules in the given path and the module
352 Parameters that are not set by the user are suppressed by default.
355 defaults: Set it to True to print also the parameters with default values
356 description: Set to True to print the descriptions of modules and
358 indentation: an internal parameter to indent the whole output
359 (needed for outputting sub-paths)
360 title: show the title string or not (defaults to True)
364 pybasf2.B2INFO(
'Modules and parameter settings in the path:')
367 indentation_string =
' ' * indentation
369 for module
in path.modules():
370 out = indentation_string +
' % 2d. % s' % (index, module.name())
372 out +=
' #%s' % module.description()
375 for param
in module.available_params():
376 if not defaults
and param.values == param.default:
378 out = indentation_string +
' %s=%s' % (param.name, param.values)
380 out +=
' #%s' % param.description
383 for condition
in module.get_all_conditions():
384 out =
"\n" + indentation_string +
' ' + str(condition) +
":"
386 print_path(condition.get_path(), defaults=defaults, description=description, indentation=indentation + 6,
390 def is_mod_function(mod, func):
391 """Return true if ``func`` is a function and defined in the module ``mod``"""
392 return _inspect.isfunction(func)
and _inspect.getmodule(func) == mod
395 def list_functions(mod):
397 Returns list of function names defined in the given Python module.
399 return [func.__name__
for func
in mod.__dict__.values()
if is_mod_function(mod, func)]
402 def pretty_print_module(module, module_name, replacements={}):
403 """Pretty print the contents of a python module.
404 It will print all the functions defined in the given module to the console
407 module: instance of the module or name with which it can be found in
409 module_name: readable module name
410 replacements (dict): dictionary containing text replacements: Every
411 occurrence of any key in the function signature will be replaced by
414 from terminal_utils
import Pager
418 if isinstance(module, str):
420 module = sys.modules[module]
422 for function_name
in sorted(list_functions(module), key=
lambda x: x.lower()):
423 function = getattr(module, function_name)
424 signature = _inspect.formatargspec(*_inspect.getfullargspec(function))
425 for key, value
in replacements.items():
426 signature = signature.replace(key, value)
427 desc_list.append((function.__name__, signature +
'\n' + function.__doc__))
429 with Pager(
'List of available functions in ' + module_name,
True):
430 pretty_print_description_list(desc_list)