# Path: uqbar/io/RedirectedStreams.py
# class RedirectedStreams:
#     """
#     A context manager for capturing ``stdout`` and ``stderr`` output.
# 
#     ..  container:: example
# 
#         ::
# 
#             >>> import uqbar.io
#             >>> import io
# 
#         ::
# 
#             >>> string_io = io.StringIO()
#             >>> with uqbar.io.RedirectedStreams(stdout=string_io):
#             ...     print("hello, world!")
#             ...
# 
#         ::
# 
#             >>> result = string_io.getvalue()
#             >>> string_io.close()
#             >>> print(result)
#             hello, world!
# 
#     This context manager is not reentrant. Use a separate instance when nesting
#     multiple timers.
#     """
# 
#     ### INITIALIZER ###
# 
#     def __init__(self, stdout=None, stderr=None):
#         self._stdout = stdout or sys.stdout
#         self._stderr = stderr or sys.stderr
# 
#     ### SPECIAL METHODS ###
# 
#     def __enter__(self):
#         self._old_stdout, self._old_stderr = sys.stdout, sys.stderr
#         self._old_stdout.flush()
#         self._old_stderr.flush()
#         sys.stdout, sys.stderr = self._stdout, self._stderr
#         return self
# 
#     def __exit__(self, exc_type, exc_value, traceback):
#         try:
#             self._stdout.flush()
#             self._stderr.flush()
#         finally:
#             sys.stdout = self._old_stdout
#             sys.stderr = self._old_stderr
# 
#     ### PUBLIC PROPERTIES ###
# 
#     @property
#     def stderr(self):
#         return self._stderr
# 
#     @property
#     def stdout(self):
#         return self._stdout


import code
import inspect
import itertools
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Tuple
from uqbar.io import RedirectedStreams
unset = object()


class MonkeyPatch(object):
    def __init__(self):
        self._attributes = []

    def setattr(self, target, name, value=unset):
        oldval = getattr(target, name, unset)
        if inspect.isclass(target):
            oldval = target.__dict__.get(name, unset)
        self._attributes.append((target, name, oldval))
        setattr(target, name, value)

    def undo(self):
        for obj, name, value in reversed(self._attributes):
            if value is not unset:
                setattr(obj, name, value)
            else:
                delattr(obj, name)
        self._attributes[:] = []


class Console(code.InteractiveConsole):
    r"""
    Interactive console providing a sandboxed namespace for executing code
    examples.

    .. container:: example


# Path: uqbar/io/RedirectedStreams.py
# class RedirectedStreams:
#     """
#     A context manager for capturing ``stdout`` and ``stderr`` output.
# 
#     ..  container:: example
# 
#         ::
# 
#             >>> import uqbar.io
#             >>> import io
# 
#         ::
# 
#             >>> string_io = io.StringIO()
#             >>> with uqbar.io.RedirectedStreams(stdout=string_io):
#             ...     print("hello, world!")
#             ...
# 
#         ::
# 
#             >>> result = string_io.getvalue()
#             >>> string_io.close()
#             >>> print(result)
#             hello, world!
# 
#     This context manager is not reentrant. Use a separate instance when nesting
#     multiple timers.
#     """
# 
#     ### INITIALIZER ###
# 
#     def __init__(self, stdout=None, stderr=None):
#         self._stdout = stdout or sys.stdout
#         self._stderr = stderr or sys.stderr
# 
#     ### SPECIAL METHODS ###
# 
#     def __enter__(self):
#         self._old_stdout, self._old_stderr = sys.stdout, sys.stderr
#         self._old_stdout.flush()
#         self._old_stderr.flush()
#         sys.stdout, sys.stderr = self._stdout, self._stderr
#         return self
# 
#     def __exit__(self, exc_type, exc_value, traceback):
#         try:
#             self._stdout.flush()
#             self._stderr.flush()
#         finally:
#             sys.stdout = self._old_stdout
#             sys.stderr = self._old_stderr
# 
#     ### PUBLIC PROPERTIES ###
# 
#     @property
#     def stderr(self):
#         return self._stderr
# 
#     @property
#     def stdout(self):
#         return self._stdout


# Path: uqbar/book/console.py
import code
import inspect
import itertools
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Tuple
from uqbar.io import RedirectedStreams

unset = object()


class MonkeyPatch(object):
    def __init__(self):
        self._attributes = []

    def setattr(self, target, name, value=unset):
        oldval = getattr(target, name, unset)
        if inspect.isclass(target):
            oldval = target.__dict__.get(name, unset)
        self._attributes.append((target, name, oldval))
        setattr(target, name, value)

    def undo(self):
        for obj, name, value in reversed(self._attributes):
            if value is not unset:
                setattr(obj, name, value)
            else:
                delattr(obj, name)
        self._attributes[:] = []


class Console(code.InteractiveConsole):
    r"""
    Interactive console providing a sandboxed namespace for executing code
    examples.

    .. container:: example


        ::