|  | """Save DOT code objects, render with Graphviz dot, and open in viewer.""" | 
					
						
						|  |  | 
					
						
						|  | import locale | 
					
						
						|  | import logging | 
					
						
						|  | import os | 
					
						
						|  | import typing | 
					
						
						|  |  | 
					
						
						|  | from .encoding import DEFAULT_ENCODING | 
					
						
						|  | from . import _tools | 
					
						
						|  | from . import saving | 
					
						
						|  | from . import jupyter_integration | 
					
						
						|  | from . import piping | 
					
						
						|  | from . import rendering | 
					
						
						|  | from . import unflattening | 
					
						
						|  |  | 
					
						
						|  | __all__ = ['Source'] | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | log = logging.getLogger(__name__) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | class Source(rendering.Render, saving.Save, | 
					
						
						|  | jupyter_integration.JupyterIntegration, piping.Pipe, | 
					
						
						|  | unflattening.Unflatten): | 
					
						
						|  | """Verbatim DOT source code string to be rendered by Graphviz. | 
					
						
						|  |  | 
					
						
						|  | Args: | 
					
						
						|  | source: The verbatim DOT source code string. | 
					
						
						|  | filename: Filename for saving the source (defaults to ``'Source.gv'``). | 
					
						
						|  | directory: (Sub)directory for source saving and rendering. | 
					
						
						|  | format: Rendering output format (``'pdf'``, ``'png'``, ...). | 
					
						
						|  | engine: Layout engine used (``'dot'``, ``'neato'``, ...). | 
					
						
						|  | encoding: Encoding for saving the source. | 
					
						
						|  |  | 
					
						
						|  | Note: | 
					
						
						|  | All parameters except ``source`` are optional. All of them | 
					
						
						|  | can be changed under their corresponding attribute name | 
					
						
						|  | after instance creation. | 
					
						
						|  | """ | 
					
						
						|  |  | 
					
						
						|  | @classmethod | 
					
						
						|  | @_tools.deprecate_positional_args(supported_number=2) | 
					
						
						|  | def from_file(cls, filename: typing.Union[os.PathLike, str], | 
					
						
						|  | directory: typing.Union[os.PathLike, str, None] = None, | 
					
						
						|  | format: typing.Optional[str] = None, | 
					
						
						|  | engine: typing.Optional[str] = None, | 
					
						
						|  | encoding: typing.Optional[str] = DEFAULT_ENCODING, | 
					
						
						|  | renderer: typing.Optional[str] = None, | 
					
						
						|  | formatter: typing.Optional[str] = None) -> 'Source': | 
					
						
						|  | """Return an instance with the source string read from the given file. | 
					
						
						|  |  | 
					
						
						|  | Args: | 
					
						
						|  | filename: Filename for loading/saving the source. | 
					
						
						|  | directory: (Sub)directory for source loading/saving and rendering. | 
					
						
						|  | format: Rendering output format (``'pdf'``, ``'png'``, ...). | 
					
						
						|  | engine: Layout command used (``'dot'``, ``'neato'``, ...). | 
					
						
						|  | encoding: Encoding for loading/saving the source. | 
					
						
						|  | """ | 
					
						
						|  | directory = _tools.promote_pathlike_directory(directory) | 
					
						
						|  | filepath = (os.path.join(directory, filename) if directory.parts | 
					
						
						|  | else os.fspath(filename)) | 
					
						
						|  |  | 
					
						
						|  | if encoding is None: | 
					
						
						|  | encoding = locale.getpreferredencoding() | 
					
						
						|  |  | 
					
						
						|  | log.debug('read %r with encoding %r', filepath, encoding) | 
					
						
						|  | with open(filepath, encoding=encoding) as fd: | 
					
						
						|  | source = fd.read() | 
					
						
						|  |  | 
					
						
						|  | return cls(source, | 
					
						
						|  | filename=filename, directory=directory, | 
					
						
						|  | format=format, engine=engine, encoding=encoding, | 
					
						
						|  | renderer=renderer, formatter=formatter, | 
					
						
						|  | loaded_from_path=filepath) | 
					
						
						|  |  | 
					
						
						|  | @_tools.deprecate_positional_args(supported_number=2) | 
					
						
						|  | def __init__(self, source: str, | 
					
						
						|  | filename: typing.Union[os.PathLike, str, None] = None, | 
					
						
						|  | directory: typing.Union[os.PathLike, str, None] = None, | 
					
						
						|  | format: typing.Optional[str] = None, | 
					
						
						|  | engine: typing.Optional[str] = None, | 
					
						
						|  | encoding: typing.Optional[str] = DEFAULT_ENCODING, *, | 
					
						
						|  | renderer: typing.Optional[str] = None, | 
					
						
						|  | formatter: typing.Optional[str] = None, | 
					
						
						|  | loaded_from_path: typing.Optional[os.PathLike] = None) -> None: | 
					
						
						|  | super().__init__(filename=filename, directory=directory, | 
					
						
						|  | format=format, engine=engine, | 
					
						
						|  | renderer=renderer, formatter=formatter, | 
					
						
						|  | encoding=encoding) | 
					
						
						|  | self._loaded_from_path = loaded_from_path | 
					
						
						|  | self._source = source | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | _source: str | 
					
						
						|  | _loaded_from_path: typing.Optional[os.PathLike] | 
					
						
						|  |  | 
					
						
						|  | def _copy_kwargs(self, **kwargs): | 
					
						
						|  | """Return the kwargs to create a copy of the instance.""" | 
					
						
						|  | return super()._copy_kwargs(source=self._source, | 
					
						
						|  | loaded_from_path=self._loaded_from_path, | 
					
						
						|  | **kwargs) | 
					
						
						|  |  | 
					
						
						|  | def __iter__(self) -> typing.Iterator[str]: | 
					
						
						|  | r"""Yield the DOT source code read from file line by line. | 
					
						
						|  |  | 
					
						
						|  | Yields: Line ending with a newline (``'\n'``). | 
					
						
						|  | """ | 
					
						
						|  | lines = self._source.splitlines(keepends=True) | 
					
						
						|  | yield from lines[:-1] | 
					
						
						|  | for line in lines[-1:]: | 
					
						
						|  | suffix = '\n' if not line.endswith('\n') else '' | 
					
						
						|  | yield line + suffix | 
					
						
						|  |  | 
					
						
						|  | @property | 
					
						
						|  | def source(self) -> str: | 
					
						
						|  | """The DOT source code as string. | 
					
						
						|  |  | 
					
						
						|  | Normalizes so that the string always ends in a final newline. | 
					
						
						|  | """ | 
					
						
						|  | source = self._source | 
					
						
						|  | if not source.endswith('\n'): | 
					
						
						|  | source += '\n' | 
					
						
						|  | return source | 
					
						
						|  |  | 
					
						
						|  | @_tools.deprecate_positional_args(supported_number=2) | 
					
						
						|  | def save(self, filename: typing.Union[os.PathLike, str, None] = None, | 
					
						
						|  | directory: typing.Union[os.PathLike, str, None] = None, *, | 
					
						
						|  | skip_existing: typing.Optional[bool] = None) -> str: | 
					
						
						|  | """Save the DOT source to file. Ensure the file ends with a newline. | 
					
						
						|  |  | 
					
						
						|  | Args: | 
					
						
						|  | filename: Filename for saving the source (defaults to ``name`` + ``'.gv'``) | 
					
						
						|  | directory: (Sub)directory for source saving and rendering. | 
					
						
						|  | skip_existing: Skip write if file exists (default: ``None``). | 
					
						
						|  | By default skips if instance was loaded from the target path: | 
					
						
						|  | ``.from_file(self.filepath)``. | 
					
						
						|  |  | 
					
						
						|  | Returns: | 
					
						
						|  | The (possibly relative) path of the saved source file. | 
					
						
						|  | """ | 
					
						
						|  | skip = (skip_existing is None and self._loaded_from_path | 
					
						
						|  | and os.path.samefile(self._loaded_from_path, self.filepath)) | 
					
						
						|  | if skip: | 
					
						
						|  | log.debug('.save(skip_existing=None) skip writing Source.from_file(%r)', | 
					
						
						|  | self.filepath) | 
					
						
						|  | return super().save(filename=filename, directory=directory, | 
					
						
						|  | skip_existing=skip) | 
					
						
						|  |  |