12basf2.utils - Helper functions for printing basf2 objects
13---------------------------------------------------------
15This modules contains some utility functions used by basf2, mainly for printing
19import inspect
as _inspect
20from shutil
import get_terminal_size
as _get_terminal_size
21import textwrap
as _textwrap
25def get_terminal_width():
27 Returns width of terminal in characters,
or 80
if unknown.
29 return _get_terminal_size(fallback=(80, 24)).columns
32def pretty_print_table(table, column_widths, first_row_is_heading=True, transform=None, min_flexible_width=10, *,
33 hline_formatter=None):
35 Pretty print a given table, by using available terminal size
and
36 word wrapping fields
as needed.
39 table: A 2d list of table fields. Each row must have the same length.
41 column_width: list of column widths, needs to be of same length
as rows
42 in 'table'. Available fields are
45 as needed, up to n characters, word wrap
if longer
48 as long
as needed, no wrapping
54 use all available space, good
for description fields.
55 If more than one column has a * they all get equal width
58 use all available space but at least the actual width. Only useful
59 to make the table span the full width of the terminal
61 The column width can also start
with ``>``, ``<``
or ``^``
in which case
62 it will be right, left
or center aligned.
64 first_row_is_heading: header specifies
if we should take the first row
65 as table header
and offset it a bit
67 transform: either
None or a callback function which takes three
70 1. the elements of the row
as a list
71 2. second the width of each column (without separator)
72 3. the preformatted text line.
74 It should
return a string representing the final line to be
77 min_flexible_width: the minimum amount of characters
for every column
80 hline_formatter: A callable function to format horizontal lines (above
and
81 below the table header). Should be a callback
with one parameter
for
82 the total width of the table
in characters
and return a string that
83 is the horizontal line. If
None is returned no line
is printed.
85 If argument
is not given
or given
as None the default of printing
'-'
86 signs are printed over the whole table width
is used.
88 .. versionchanged:: after release 5
89 Added support
for column alignment
93 act_column_widths = [len(cell)
for cell
in table[0]]
95 for (col, cell)
in enumerate(row):
96 act_column_widths[col] = max(len(str(cell)), act_column_widths[col])
103 for (col, opt)
in enumerate(column_widths):
105 if isinstance(opt, str)
and opt[0]
in "<>^":
118 long_columns.append(col)
123 long_columns.append(- col - 1)
125 elif isinstance(opt, int)
and opt > 0:
127 act_column_widths[col] = opt
128 elif isinstance(opt, int)
and opt == 0:
131 elif isinstance(opt, int)
and opt < 0:
133 act_column_widths[col] = min(act_column_widths[col], -opt)
135 print(
'Invalid column_widths option "' + str(opt) +
'"')
137 total_used_width += act_column_widths[col]
140 total_used_width += len(act_column_widths) - 1
143 remaining_space = max(get_terminal_width() - total_used_width, len(long_columns) * min_flexible_width)
145 col_width, remainder = divmod(remaining_space, len(long_columns))
146 for i, col
in enumerate(long_columns):
147 size = col_width + (1
if i < remainder
else 0)
153 act_column_widths[col] = max(size, act_column_widths[col])
156 total_used_width += act_column_widths[col] - size
158 act_column_widths[col] = size
160 total_used_width += remaining_space
162 format_string =
' '.join([
'{:%s%d}' % opt
for opt
in zip(align, act_column_widths[:-1])])
165 format_string +=
' {}'
167 format_string +=
' {:%s%d}' % (align[-1], act_column_widths[-1])
169 if hline_formatter
is not None:
170 hline = hline_formatter(total_used_width)
172 hline = total_used_width *
"-"
175 if first_row_is_heading
and hline
is not None:
181 wrapped_row = [_textwrap.wrap(str(row[i]), width)
for (i, width)
in
182 enumerate(act_column_widths)]
183 max_lines = max([len(col)
for col
in wrapped_row])
184 for line
in range(max_lines):
185 for (i, cell)
in enumerate(row):
186 if line < len(wrapped_row[i]):
187 row[i] = wrapped_row[i][line]
190 line = format_string.format(*row)
191 if transform
is not None:
192 line = transform(row, act_column_widths, line)
195 if not header_shown
and first_row_is_heading
and hline
is not None:
200def pretty_print_description_list(rows):
202 Given a list of 2-tuples, print a nicely formatted description list.
203 Rows
with only one entry are interpreted
as sub-headings
205 term_width = get_terminal_width()
209 wrapper = _textwrap.TextWrapper(width=term_width, initial_indent=
"",
210 subsequent_indent=
" " * (module_width))
212 useColors = pybasf2.LogPythonInterface.terminal_supports_colors()
215 """Use ANSI sequence to show string in bold"""
217 return '\x1b[1m' + text +
'\x1b[0m'
221 print(term_width *
'-')
227 print(bold(subheading).center(term_width))
229 name, description = row
230 for i, line
in enumerate(description.splitlines()):
235 wrapper.initial_indent = max(module_width, len(name) + 1) *
" "
236 print(bold(name.ljust(module_width - 1)), wrapper.fill(line).lstrip())
240 wrapper.initial_indent = wrapper.subsequent_indent
241 print(wrapper.fill(line))
243 name, description, vartype = row
244 for i, line
in enumerate(description.splitlines()):
246 wrapper.initial_indent = max(module_width, len(name + vartype) + 4) *
" "
247 print(bold((name+
' ['+vartype+
']').ljust(module_width - 1)), wrapper.fill(line).lstrip())
249 wrapper.initial_indent = wrapper.subsequent_indent
250 print(wrapper.fill(line))
252 print(term_width *
'-')
256def print_all_modules(moduleList, package=''):
258 Loop over the list of available modules,
259 register them and print their information
265 for moduleName
in moduleList:
267 current_module = pybasf2._register_module(moduleName)
268 if package ==
'' or current_module.package() == package:
269 modules.append((current_module.package(), moduleName, current_module.description()))
270 except pybasf2.ModuleNotCreatedError:
271 pybasf2.B2ERROR(f
'The module {moduleName} could not be loaded.')
273 except Exception
as e:
274 pybasf2.B2ERROR(f
'An exception occurred when trying to load the module {moduleName}: {e}')
279 for (packageName, moduleName, description)
in sorted(modules):
280 if current_package != packageName:
281 current_package = packageName
282 table.append((current_package,))
283 table.append((moduleName, description))
284 if package !=
'' and len(table) == 0:
285 pybasf2.B2FATAL(
'Print module information: No module or package named "' +
286 package +
'" found!')
288 pretty_print_description_list(table)
290 print(
'To show detailed information on a module, including its parameters,')
291 print(
"type \'basf2 -m ModuleName\'. Use \'basf2 -m package\' to only list")
292 print(
'modules belonging to a given package.')
295 pybasf2.B2FATAL(
"One or more modules could not be loaded. Please check the "
296 "following ERROR messages and contact the responsible authors.")
299def print_params(module, print_values=True, shared_lib_path=None):
301 This function prints parameter information
304 module: Print the parameter information of this module
305 print_values: Set it to True to
print the current values of the parameters
306 shared_lib_path: The path of the shared library
from which the module was
311 print(
'=' * (len(module.name()) + 4))
312 print(f
' {module.name()}')
313 print(
'=' * (len(module.name()) + 4))
314 print(f
'Description: {module.description()}')
315 if shared_lib_path
is not None:
316 print(f
'Found in: {shared_lib_path}')
317 print(f
'Package: {module.package()}')
330 output.append([
'Parameter',
'Type',
'Default',
'Description'])
332 has_forced_params =
False
333 paramList = module.available_params()
334 for paramItem
in paramList:
335 defaultStr = str(paramItem.default)
336 valueStr = str(paramItem.values)
338 if paramItem.forceInSteering:
340 has_forced_params =
True
344 forceString + paramItem.name,
348 paramItem.setInSteering,
349 paramItem.description])
351 output.append([forceString + paramItem.name, paramItem.type,
352 defaultStr, paramItem.description])
354 column_widths = [-25] * len(output[0])
355 column_widths[2] = -20
356 column_widths[-1] =
'*'
358 pretty_print_table(output, column_widths)
360 if has_forced_params:
361 print(
' * denotes a required parameter.')
364def print_path(path, defaults=False, description=False, indentation=0, title=True):
366 This function prints the modules in the given path
and the module
368 Parameters that are
not set by the user are suppressed by default.
371 defaults: Set it to
True to
print also the parameters
with default values
372 description: Set to
True to
print the descriptions of modules
and
374 indentation: an internal parameter to indent the whole output
375 (needed
for outputting sub-paths)
376 title: show the title string
or not (defaults to
True)
380 pybasf2.B2INFO(
'Modules and parameter settings in the path:')
383 indentation_string =
' ' * indentation
385 for module
in path.modules():
386 out = indentation_string +
' % 2d. % s' % (index, module.name())
388 out += f
' #{module.description()}'
391 for param
in module.available_params():
392 if not defaults
and param.values == param.default:
394 out = indentation_string + f
' {param.name}={param.values}'
396 out += f
' #{param.description}'
399 for condition
in module.get_all_conditions():
400 out =
"\n" + indentation_string +
' ' + str(condition) +
":"
402 print_path(condition.get_path(), defaults=defaults, description=description, indentation=indentation + 6,
406def is_mod_function(mod, func):
407 """Return true if ``func`` is a function and defined in the module ``mod``"""
408 return _inspect.isfunction(func)
and _inspect.getmodule(func) == mod
411def list_functions(mod):
413 Returns list of function names defined in the given Python module.
415 return [func.__name__
for func
in mod.__dict__.values()
if is_mod_function(mod, func)]
418def pretty_print_module(module, module_name, replacements=None):
419 """Pretty print the contents of a python module.
420 It will print all the functions defined
in the given module to the console
423 module: instance of the module
or name
with which it can be found
in
425 module_name: readable module name
426 replacements (dict): dictionary containing text replacements: Every
427 occurrence of any key
in the function signature will be replaced by
430 from terminal_utils
import Pager
433 if replacements
is None:
437 if isinstance(module, str):
439 module = sys.modules[module]
441 for function_name
in sorted(list_functions(module), key=
lambda x: x.lower()):
442 function = getattr(module, function_name)
443 signature = str(_inspect.signature(function))
444 for key, value
in replacements.items():
445 signature = signature.replace(key, value)
446 function_doc = function.__doc__
448 function_doc =
'(no documentation)'
449 desc_list.append((function.__name__, signature +
'\n' + function_doc))
451 with Pager(
'List of available functions in ' + module_name,
True):
452 pretty_print_description_list(desc_list)