#!/usr/bin/env python
#
# terminal.py
"""
Useful functions for terminal-based programs.
.. versionchanged:: 2.0.0
:func:`domdf_python_tools.terminal.get_terminal_size` was removed.
Use :func:`shutil.get_terminal_size` instead.
"""
#
# Copyright © 2014-2020 Dominic Davis-Foster <dominic@davis-foster.co.uk>
#
# Parts of the docstrings based on the Python 3.8.2 Documentation
# Licensed under the Python Software Foundation License Version 2.
# Copyright © 2001-2020 Python Software Foundation. All rights reserved.
# Copyright © 2000 BeOpen.com. All rights reserved.
# Copyright © 1995-2000 Corporation for National Research Initiatives. All rights reserved.
# Copyright © 1991-1995 Stichting Mathematisch Centrum. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
# OR OTHER DEALINGS IN THE SOFTWARE.
#
# "Echo" based on ChemPy (https://github.com/bjodah/chempy)
# | Copyright (c) 2015-2018, Björn Dahlgren
# | All rights reserved.
# |
# | Redistribution and use in source and binary forms, with or without modification,
# | are permitted provided that the following conditions are met:
# |
# | Redistributions of source code must retain the above copyright notice, this
# | list of conditions and the following disclaimer.
# |
# | Redistributions in binary form must reproduce the above copyright notice, this
# | list of conditions and the following disclaimer in the documentation and/or
# | other materials provided with the distribution.
# |
# | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
# | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# stdlib
import inspect
import os
import pprint
import textwrap
from shutil import get_terminal_size
from typing import IO, Optional
# this package
from domdf_python_tools.words import CR
__all__ = [
"clear",
"br",
"interrupt",
"overtype",
"get_terminal_size",
"Echo",
]
[docs]def clear() -> None:
"""
Clears the display.
Works for Windows and POSIX, but does not clear the Python Interpreter or PyCharm's Console.
"""
if os.name == "nt": # pragma: no cover (!Windows)
os.system("cls") # nosec: B607,B605
else: # pragma: no cover (!Linux)
print("\u001bc", end='')
[docs]def br() -> None:
"""
Prints a blank line.
"""
print('')
[docs]def interrupt() -> None:
"""
Print the key combination needed to abort the script; dynamic depending on OS.
Useful when you have a long-running script that you might want to
interrupt part way through.
**Example:**
.. code-block:: python
>>> interrupt()
(Press Ctrl-C to quit at any time)
"""
print(f"(Press Ctrl-{'C' if os.name == 'nt' else 'D'} to quit at any time)")
[docs]def overtype(
*objects,
sep: str = ' ',
end: str = '',
file: Optional[IO] = None,
flush: bool = False,
) -> None:
r"""
Print ``*objects`` to the text stream ``file``, starting with ``'\\r'``, separated by ``sep``
and followed by ``end``.
All non-keyword arguments are converted to strings like :class:`str` does and written to the stream,
separated by ``sep`` and followed by ``end``.
If no objects are given, :func:`~.overtype` will just write ``"\\r"``.
.. TODO:: This does not currently work in the PyCharm console, at least on Windows
:param \*objects: A list of strings or string-like objects to write to the terminal.
:param sep: The separator between values.
:param end: The final value to print.
:param file: An object with a ``write(string)`` method.
If not present or :py:obj:`None`, :py:obj:`sys.stdout` will be used.
:no-default file:
:param flush: If :py:obj:`True` the stream is forcibly flushed after printing.
""" # noqa: D400
object0 = f"{CR}{objects[0]}"
objects = (object0, *objects[1:])
print(*objects, sep=sep, end=end, file=file, flush=flush)
[docs]class Echo:
"""
Context manager for echoing variable assignments (in CPython).
:param indent: The indentation of the dictionary of variable assignments.
"""
def __init__(self, indent: str = ' ' * 2):
self.indent = indent
frame = inspect.currentframe()
if frame is None: # pragma: no cover
raise ValueError("Unable to obtain the frame of the caller.")
else:
self.parent_frame = inspect.currentframe().f_back # type: ignore # TODO
[docs] def __enter__(self):
"""
Called when entering the context manager.
"""
self.locals_on_entry = self.parent_frame.f_locals.copy() # type: ignore
[docs] def __exit__(self, *args, **kwargs):
"""
Called when exiting the context manager.
"""
new_locals = {
k: v
for k,
v in self.parent_frame.f_locals.items() # type: ignore
if k not in self.locals_on_entry
}
print(textwrap.indent(pprint.pformat(new_locals), self.indent))
if __name__ == "__main__": # pragma: no cover
size_x, size_y = get_terminal_size()
print("width =", size_x, "height =", size_y)