12 basf2.utils - Helper functions for printing basf2 objects
13 ---------------------------------------------------------
15 This modules contains some utility functions used by basf2, mainly for printing
19 import inspect
as _inspect
20 from shutil
import get_terminal_size
as _get_terminal_size
21 import textwrap
as _textwrap
25 def get_terminal_width():
27 Returns width of terminal in characters, or 80 if unknown.
29 return _get_terminal_size(fallback=(80, 24)).columns
32 def 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:
200 def 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 print(term_width *
'-')
247 def print_all_modules(moduleList, package=''):
249 Loop over the list of available modules,
250 register them and print their information
256 for moduleName
in moduleList:
258 current_module = pybasf2._register_module(moduleName)
259 if package ==
'' or current_module.package() == package:
260 modules.append((current_module.package(), moduleName, current_module.description()))
261 except pybasf2.ModuleNotCreatedError:
262 pybasf2.B2ERROR(f
'The module {moduleName} could not be loaded.')
264 except Exception
as e:
265 pybasf2.B2ERROR(f
'An exception occurred when trying to load the module {moduleName}: {e}')
270 for (packageName, moduleName, description)
in sorted(modules):
271 if current_package != packageName:
272 current_package = packageName
273 table.append((current_package,))
274 table.append((moduleName, description))
275 if package !=
'' and len(table) == 0:
276 pybasf2.B2FATAL(
'Print module information: No module or package named "' +
277 package +
'" found!')
279 pretty_print_description_list(table)
281 print(
'To show detailed information on a module, including its parameters,')
282 print(
"type \'basf2 -m ModuleName\'. Use \'basf2 -m package\' to only list")
283 print(
'modules belonging to a given package.')
286 pybasf2.B2FATAL(
"One or more modules could not be loaded. Please check the "
287 "following ERROR messages and contact the responsible authors.")
290 def print_params(module, print_values=True, shared_lib_path=None):
292 This function prints parameter information
295 module: Print the parameter information of this module
296 print_values: Set it to True to print the current values of the parameters
297 shared_lib_path: The path of the shared library from which the module was
302 print(
'=' * (len(module.name()) + 4))
303 print(
' %s' % module.name())
304 print(
'=' * (len(module.name()) + 4))
305 print(
'Description: %s' % module.description())
306 if shared_lib_path
is not None:
307 print(
'Found in: %s' % shared_lib_path)
308 print(
'Package: %s' % module.package())
321 output.append([
'Parameter',
'Type',
'Default',
'Description'])
323 has_forced_params =
False
324 paramList = module.available_params()
325 for paramItem
in paramList:
326 defaultStr = str(paramItem.default)
327 valueStr = str(paramItem.values)
329 if paramItem.forceInSteering:
331 has_forced_params =
True
335 forceString + paramItem.name,
339 paramItem.setInSteering,
340 paramItem.description])
342 output.append([forceString + paramItem.name, paramItem.type,
343 defaultStr, paramItem.description])
345 column_widths = [-25] * len(output[0])
346 column_widths[2] = -20
347 column_widths[-1] =
'*'
349 pretty_print_table(output, column_widths)
351 if has_forced_params:
352 print(
' * denotes a required parameter.')
355 def print_path(path, defaults=False, description=False, indentation=0, title=True):
357 This function prints the modules in the given path and the module
359 Parameters that are not set by the user are suppressed by default.
362 defaults: Set it to True to print also the parameters with default values
363 description: Set to True to print the descriptions of modules and
365 indentation: an internal parameter to indent the whole output
366 (needed for outputting sub-paths)
367 title: show the title string or not (defaults to True)
371 pybasf2.B2INFO(
'Modules and parameter settings in the path:')
374 indentation_string =
' ' * indentation
376 for module
in path.modules():
377 out = indentation_string +
' % 2d. % s' % (index, module.name())
379 out +=
' #%s' % module.description()
382 for param
in module.available_params():
383 if not defaults
and param.values == param.default:
385 out = indentation_string + f
' {param.name}={param.values}'
387 out +=
' #%s' % param.description
390 for condition
in module.get_all_conditions():
391 out =
"\n" + indentation_string +
' ' + str(condition) +
":"
393 print_path(condition.get_path(), defaults=defaults, description=description, indentation=indentation + 6,
397 def is_mod_function(mod, func):
398 """Return true if ``func`` is a function and defined in the module ``mod``"""
399 return _inspect.isfunction(func)
and _inspect.getmodule(func) == mod
402 def list_functions(mod):
404 Returns list of function names defined in the given Python module.
406 return [func.__name__
for func
in mod.__dict__.values()
if is_mod_function(mod, func)]
409 def pretty_print_module(module, module_name, replacements=None):
410 """Pretty print the contents of a python module.
411 It will print all the functions defined in the given module to the console
414 module: instance of the module or name with which it can be found in
416 module_name: readable module name
417 replacements (dict): dictionary containing text replacements: Every
418 occurrence of any key in the function signature will be replaced by
421 from terminal_utils
import Pager
424 if replacements
is None:
428 if isinstance(module, str):
430 module = sys.modules[module]
432 for function_name
in sorted(list_functions(module), key=
lambda x: x.lower()):
433 function = getattr(module, function_name)
434 signature = _inspect.formatargspec(*_inspect.getfullargspec(function))
435 for key, value
in replacements.items():
436 signature = signature.replace(key, value)
437 desc_list.append((function.__name__, signature +
'\n' + function.__doc__))
439 with Pager(
'List of available functions in ' + module_name,
True):
440 pretty_print_description_list(desc_list)