1"""Return the version number from running ``dot -V``."""
2
3import logging
4import re
5import subprocess
6import typing
7
8from . import dot_command
9from . import execute
10
11VERSION_PATTERN = re.compile(r'''
12 graphviz[ ]
13 version[ ]
14 (\d+)\.(\d+)
15 (?:\.(\d+)
16 (?:
17 ~dev\.\d{8}\.\d{4}
18 |
19 \.(\d+)
20 )?
21 )?
22 [ ]
23 ''', re.VERBOSE)
24
25
26log = logging.getLogger(__name__)
27
28
29def version() -> typing.Tuple[int, ...]:
30 """Return the upstream version number tuple from ``stderr`` of ``dot -V``.
31
32 Returns:
33 Two, three, or four ``int`` version ``tuple``.
34
35 Raises:
36 graphviz.ExecutableNotFound: If the Graphviz executable is not found.
37 graphviz.CalledProcessError: If the exit status is non-zero.
38 RuntimeError: If the output cannot be parsed into a version number.
39
40 Example:
41 >>> doctest_mark_exe()
42 >>> import graphviz
43 >>> graphviz.version() # doctest: +ELLIPSIS
44 (...)
45
46 Note:
47 Ignores the ``~dev.<YYYYmmdd.HHMM>`` portion of development versions.
48
49 See also:
50 Upstream release version entry format:
51 https://gitlab.com/graphviz/graphviz/-/blob/f94e91ba819cef51a4b9dcb2d76153684d06a913/gen_version.py#L17-20
52 """
53 cmd = [dot_command.DOT_BINARY, '-V']
54 proc = execute.run_check(cmd,
55 stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
56 encoding='ascii')
57
58 ma = VERSION_PATTERN.search(proc.stdout)
59 if ma is None:
60 raise RuntimeError(f'cannot parse {cmd!r} output: {proc.stdout!r}')
61
62 return tuple(int(d) for d in ma.groups() if d is not None)