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)