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)