# Copyright French Alternative Energies and Atomic Energy Commission
# Contributors: valjean developers
# valjean-support@cea.fr
#
# This software is a computer program whose purpose is to analyze and
# post-process numerical simulation results.
#
# This software is governed by the CeCILL license under French law and abiding
# by the rules of distribution of free software. You can use, modify and/ or
# redistribute the software under the terms of the CeCILL license as circulated
# by CEA, CNRS and INRIA at the following URL: http://www.cecill.info.
#
# As a counterpart to the access to the source code and rights to copy, modify
# and redistribute granted by the license, users are provided only with a
# limited warranty and the software's author, the holder of the economic
# rights, and the successive licensors have only limited liability.
#
# In this respect, the user's attention is drawn to the risks associated with
# loading, using, modifying and/or developing or reproducing the software by
# the user in light of its specific status of free software, that may mean that
# it is complicated to manipulate, and that also therefore means that it is
# reserved for developers and experienced professionals having in-depth
# computer knowledge. Users are therefore encouraged to load and test the
# software's suitability as regards their requirements in conditions enabling
# the security of their systems and/or data to be ensured and, more generally,
# to use and operate it in the same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL license and that you accept its terms.
'''Module for the ``graph`` subcommand.'''
import os
import logging
from ..common import JobCommand, build_graphs
LOGGER = logging.getLogger(__name__)
[docs]class GraphCommand(JobCommand):
'''Command class for the ``build`` subcommand.'''
NAME = 'graph'
HELP = 'Output the dependency graph in any format supported by Graphviz.'
DESC = HELP
[docs] def register(self, parser):
'''Register options for this command in the parser.'''
super().register(parser)
parser.add_argument('--dependencies', action='store',
help='select which dependencies to draw (default: '
'both)', choices=['hard', 'soft', 'both'],
default='both')
parser.add_argument('-o', '--output',
action='store', help='path to the output file')
parser.set_defaults(func=self.execute)
[docs] @staticmethod
def execute(args, _config):
'''Execute the ``graph`` command.'''
hard_graph, soft_graph = build_graphs(args)
if args.dependencies == 'hard':
graph_str = hard_graph.to_graphviz()
elif args.dependencies == 'soft':
graph_str = soft_graph.to_graphviz()
elif args.dependencies == 'both':
hard_graph_str = hard_graph.to_graphviz()
soft_graph_str = soft_graph.to_graphviz()
graph_str = GraphCommand.merge_graph_str(hard_graph_str,
soft_graph_str)
if args.output:
_, ext = os.path.splitext(args.output)
if ext:
try:
from pydot import graph_from_dot_data
except ImportError:
LOGGER.error('you need to install pydot to use the '
'--output option.')
raise
# pylint: disable-next=possibly-used-before-assignment
graph_pydot = graph_from_dot_data(graph_str)[0]
try:
writer = getattr(graph_pydot, 'write_' + ext[1:].lower())
except AttributeError as aerr:
raise ValueError(f'graph output format {ext} is not '
'supported') from aerr
writer(args.output)
else:
with open(args.output, 'w', encoding='utf-8') as fout:
fout.write(graph_str)
else:
print(graph_str)
[docs] @staticmethod
def merge_graph_str(solid_graph, dashed_graph):
r'''Merge two strings representing graphviz graphs into one graph. The
graphs are merged in such a way that the edges of `solid_graph` and
`dashed_graph` are respectively represented as solid and dashed.
.. doctest:: merge_graph_str
:hide:
>>> solid_graph = ('digraph {\n node1 -> node2;\n'
... ' node2 -> node3;\n}')
>>> dashed_graph = 'digraph {\n node1 -> node3;\n}'
For example:
>>> print(solid_graph)
digraph {
node1 -> node2;
node2 -> node3;
}
>>> print(dashed_graph)
digraph {
node1 -> node3;
}
>>> print(GraphCommand.merge_graph_str(solid_graph, dashed_graph))
digraph {
subgraph G1 {
node1 -> node2;
node2 -> node3;
}
subgraph G2 {
edge [style=dashed];
node1 -> node3;
}
}
:param str solid_graph: a graph, as produced by
:meth:`~.DepGraph.to_graphviz`.
:param str dashed_graph: another graph, as produced by
:meth:`~.DepGraph.to_graphviz`.
:returns: the merged graph.
:rtype: str
'''
from itertools import chain
lines_solid = solid_graph.split('\n')
lines_dashed = dashed_graph.split('\n')
lines = list(chain(('digraph {\n subgraph G1 {',),
lines_solid[1:-1],
(' }\n subgraph G2 {\n edge [style=dashed];',),
lines_dashed[1:-1],
(' }\n}',)))
return '\n'.join(lines)