1"""Pipe DOT source code through ``unflatten``."""
2
3import pathlib
4import os
5from typing import Final
6
7from ..encoding import DEFAULT_ENCODING
8from .. import _tools
9from .. import exceptions
10
11from . import execute
12
13__all__ = ['UNFLATTEN_BINARY', 'unflatten']
14
15UNFLATTEN_BINARY: Final = pathlib.Path('unflatten')
16
17
18@_tools.deprecate_positional_args(supported_number=1)
19def unflatten(source: str,
20 stagger: int | None = None,
21 fanout: bool = False,
22 chain: int | None = None,
23 encoding: str = DEFAULT_ENCODING) -> str:
24 """Return DOT ``source`` piped through ``unflatten`` preprocessor as string.
25
26 Args:
27 source: DOT source to process
28 (improve layout aspect ratio).
29 stagger: Stagger the minimum length of leaf edges
30 between 1 and this small integer.
31 fanout: Fanout nodes with indegree = outdegree = 1
32 when staggering (requires ``stagger``).
33 chain: Form disconnected nodes into chains of up to this many nodes.
34 encoding: Encoding to encode unflatten stdin and decode its stdout.
35
36 Returns:
37 Decoded stdout of the Graphviz unflatten command.
38
39 Raises:
40 graphviz.RequiredArgumentError: If ``fanout`` is given
41 but no ``stagger``.
42 graphviz.ExecutableNotFound: If the Graphviz 'unflatten' executable
43 is not found.
44 graphviz.CalledProcessError: If the returncode (exit status)
45 of the unflattening 'unflatten' subprocess is non-zero.
46
47 See also:
48 Upstream documentation:
49 https://www.graphviz.org/pdf/unflatten.1.pdf
50 """
51 if fanout and stagger is None:
52 raise exceptions.RequiredArgumentError('fanout given without stagger')
53
54 cmd: list[os.PathLike[str] | str] = [UNFLATTEN_BINARY]
55 if stagger is not None:
56 cmd += ['-l', str(stagger)]
57 if fanout:
58 cmd.append('-f')
59 if chain is not None:
60 cmd += ['-c', str(chain)]
61
62 proc = execute.run_check(cmd, input=source, encoding=encoding,
63 capture_output=True)
64 return proc.stdout