From 90c16ecc359055d426f4880881feccbae5997af5 Mon Sep 17 00:00:00 2001
From: Verdi March <verdimrc@users.noreply.github.com>
Date: Wed, 23 Feb 2022 18:31:05 +0800
Subject: [PATCH 1/6] my-nb-color: alternate setup

---
 {{cookiecutter.repo_name}}/src/my_nb_color.py | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/{{cookiecutter.repo_name}}/src/my_nb_color.py b/{{cookiecutter.repo_name}}/src/my_nb_color.py
index ee3dc4e..5815e5f 100644
--- a/{{cookiecutter.repo_name}}/src/my_nb_color.py
+++ b/{{cookiecutter.repo_name}}/src/my_nb_color.py
@@ -29,16 +29,21 @@
     print = pprint = oprint = print
 else:
     oprint = print  # In-case plain old behavior is needed
-    rich.reconfigure(force_terminal=True, force_jupyter=False)
+    rich.reconfigure(force_terminal=True)
     rich.pretty.install()
-    print = cast(Callable, rich.get_console().out)
-    _pprint = rich.get_console().print
+
+    _console = rich.console.Console(force_terminal=True, force_jupyter=False)
+    print = cast(Callable, _console.out)
+    _pprint = _console.print
 
     def pprint(*args, **kwargs):
         kwargs["soft_wrap"] = True
         _pprint(*args, **kwargs)
 
-    inspect = rich.inspect
+    def inspect(*args, **kwargs):
+        if "console" not in kwargs:
+            kwargs["console"] = _console
+        rich.inspect(*args, **kwargs)
 
 # Try to setup loguru.
 try:

From be884587c974998740240db3cb4d8526a950f6c3 Mon Sep 17 00:00:00 2001
From: Verdi March <verdimrc@users.noreply.github.com>
Date: Wed, 23 Feb 2022 18:35:20 +0800
Subject: [PATCH 2/6] Linted my-nb-*.py

---
 {{cookiecutter.repo_name}}/notebooks/my_nb_path.py | 2 ++
 {{cookiecutter.repo_name}}/src/my_nb_color.py      | 1 +
 2 files changed, 3 insertions(+)

diff --git a/{{cookiecutter.repo_name}}/notebooks/my_nb_path.py b/{{cookiecutter.repo_name}}/notebooks/my_nb_path.py
index 0571245..ac2ed1e 100644
--- a/{{cookiecutter.repo_name}}/notebooks/my_nb_path.py
+++ b/{{cookiecutter.repo_name}}/notebooks/my_nb_path.py
@@ -46,11 +46,13 @@
 from pathlib import Path
 from typing import Union
 
+
 def sys_path_append(o: Union[str, os.PathLike]) -> None:
     posix_path: str = o.as_posix() if isinstance(o, Path) else Path(o).as_posix()
     if posix_path not in sys.path:
         sys.path.insert(0, posix_path)
 
+
 # Add GIT_ROOT/ and a few other subdirs
 _p = subprocess.run(
     ["git", "rev-parse", "--show-toplevel"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT
diff --git a/{{cookiecutter.repo_name}}/src/my_nb_color.py b/{{cookiecutter.repo_name}}/src/my_nb_color.py
index 5815e5f..dc65274 100644
--- a/{{cookiecutter.repo_name}}/src/my_nb_color.py
+++ b/{{cookiecutter.repo_name}}/src/my_nb_color.py
@@ -45,6 +45,7 @@ def inspect(*args, **kwargs):
             kwargs["console"] = _console
         rich.inspect(*args, **kwargs)
 
+
 # Try to setup loguru.
 try:
     from loguru import logger

From 1d346d696d2bae6da3c3efa4607c16ae6bc36f9d Mon Sep 17 00:00:00 2001
From: Verdi March <verdimrc@users.noreply.github.com>
Date: Thu, 24 Feb 2022 11:52:09 +0800
Subject: [PATCH 3/6] my_nb_color.py: opinionated rich configuration

---
 {{cookiecutter.repo_name}}/src/my_nb_color.py | 93 ++++++++++++++++---
 1 file changed, 81 insertions(+), 12 deletions(-)

diff --git a/{{cookiecutter.repo_name}}/src/my_nb_color.py b/{{cookiecutter.repo_name}}/src/my_nb_color.py
index dc65274..e37cbf7 100644
--- a/{{cookiecutter.repo_name}}/src/my_nb_color.py
+++ b/{{cookiecutter.repo_name}}/src/my_nb_color.py
@@ -5,7 +5,7 @@
 Basic usage by an ``.ipynb``:
 
     >>> # Colorize notebook outputs
-    >>> from my_nb_color import print, pprint, oprint
+    >>> from my_nb_color import print, pprint, oprint, inspect
     >>>
     >>> # Test-drive different behavior of print functionalities
     >>> d = {"A" * 200, "B" * 200}
@@ -14,6 +14,13 @@
     >>> oprint("Plain (i.e., Python's original):", d)
     >>> display(d)
     >>>
+    >>> import pandas as pd
+    >>> df = pd.DataFrame(dict(a=[1,2,3], b=[4,5,6]))
+    >>> display(d, df)
+    >>> df.plot();
+    >>>
+    >>> inspect(df)
+    >>>
     >>> # Test-drive loguru
     >>> from loguru import logger
     >>> for f in (logger.debug, logger.info, logger.success, logger.error):
@@ -29,22 +36,84 @@
     print = pprint = oprint = print
 else:
     oprint = print  # In-case plain old behavior is needed
-    rich.reconfigure(force_terminal=True)
-    rich.pretty.install()
 
-    _console = rich.console.Console(force_terminal=True, force_jupyter=False)
+    rich.reconfigure(force_terminal=True, force_jupyter=False)
+    _console = rich.get_console()
+
     print = cast(Callable, _console.out)
-    _pprint = _console.print
 
-    def pprint(*args, **kwargs):
-        kwargs["soft_wrap"] = True
-        _pprint(*args, **kwargs)
+    def pprint(*args, soft_wrap=True, **kwargs):
+        """Call ``rich.console.Console(...).print(..., soft_wrap=True, ...)``."""
+        _console.print(*args, soft_wrap=soft_wrap, **kwargs)
+
+    class Inspect:
+        def __init__(self, console=_console):
+            self.console = _console
+
+        def __call__(self, obj, *args, **kwargs):
+            """Call ``rich.inspect(..., console=<preset_console>, ...)``."""
+            # Do not inspect wrappers, because *args & **kwargs are not useful for callers.
+            #
+            # Implementation notes: make sure the pattern is:
+            #
+            #   if <RHS> is <LHS>:
+            #       <LHS> = <RHS>
+            if self is obj:
+                obj = rich.inspect
+            elif pprint is obj:
+                obj = self.console.print
+
+            rich.inspect(obj, *args, console=self.console, **kwargs)
+
+    inspect = Inspect()
+
+    def opinionated_rich_pretty_install():
+        """Intercept any post-ipython renderings.
+
+        Known cases fixed (as of rich-11.2.0): (i) prevent pandas dataframe rendered twice (as text
+        and as html), (ii) do not show ``<Figure ...>`` on matplotlib figures.
+        """
+        from IPython.core.formatters import BaseFormatter
+
+        class RichFormatterWrapper(BaseFormatter):
+            # See: rich.pretty.install._ipy_display_hook()
+            reprs = [
+                "_repr_html_",
+                "_repr_markdown_",
+                "_repr_json_",
+                "_repr_latex_",
+                "_repr_jpeg_",
+                "_repr_png_",
+                "_repr_svg_",
+                "_repr_mimebundle_",
+            ]
+
+            def __init__(self, rich_formatter):
+                self.rich_formatter = rich_formatter
+
+            def __call__(self, value, *args, **kwargs):
+                for repr_name in self.reprs:
+                    try:
+                        repr_method = getattr(value, repr_name)
+                        repr_method()
+                    except (
+                        AttributeError,  # value object has does not have the repr attribute
+                        Exception,  # any other error
+                    ) as e:
+                        continue
+                    else:
+                        return
+                else:
+                    # None of the ipython methods work, hence let rich takes over
+                    self.rich_formatter(value, *args, **kwargs)
 
-    def inspect(*args, **kwargs):
-        if "console" not in kwargs:
-            kwargs["console"] = _console
-        rich.inspect(*args, **kwargs)
+        rich.pretty.install()
+        ipy_formatters = get_ipython().display_formatter.formatters
+        rich_formatter = ipy_formatters["text/plain"]
+        if rich_formatter.__module__ == "rich.pretty":
+            ipy_formatters["text/plain"] = RichFormatterWrapper(rich_formatter)
 
+    opinionated_rich_pretty_install()
 
 # Try to setup loguru.
 try:

From bccc770bd90ca009976a1567b53f53f9aaf3218e Mon Sep 17 00:00:00 2001
From: Verdi March <verdimrc@users.noreply.github.com>
Date: Thu, 24 Feb 2022 11:53:24 +0800
Subject: [PATCH 4/6] my_nb_color: added inspect examples

---
 {{cookiecutter.repo_name}}/src/my_nb_color.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/{{cookiecutter.repo_name}}/src/my_nb_color.py b/{{cookiecutter.repo_name}}/src/my_nb_color.py
index e37cbf7..c9a5f4a 100644
--- a/{{cookiecutter.repo_name}}/src/my_nb_color.py
+++ b/{{cookiecutter.repo_name}}/src/my_nb_color.py
@@ -20,6 +20,9 @@
     >>> df.plot();
     >>>
     >>> inspect(df)
+    >>> inspect(print)
+    >>> inspect(pprint)
+    >>> inspect(inspect)
     >>>
     >>> # Test-drive loguru
     >>> from loguru import logger

From 68c1e5cf4ce4146aa9df384ed362d9ccb97d0bff Mon Sep 17 00:00:00 2001
From: Verdi March <verdimrc@users.noreply.github.com>
Date: Thu, 24 Feb 2022 12:18:20 +0800
Subject: [PATCH 5/6] my_nb_color: added fallback for inspect()

---
 {{cookiecutter.repo_name}}/src/my_nb_color.py | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/{{cookiecutter.repo_name}}/src/my_nb_color.py b/{{cookiecutter.repo_name}}/src/my_nb_color.py
index c9a5f4a..86e3ddf 100644
--- a/{{cookiecutter.repo_name}}/src/my_nb_color.py
+++ b/{{cookiecutter.repo_name}}/src/my_nb_color.py
@@ -30,6 +30,7 @@
     >>>     f("Hello World!")
 """
 import sys
+import warnings
 from typing import Callable, cast
 
 # Try to setup rich.
@@ -37,6 +38,10 @@
     import rich
 except ModuleNotFoundError:
     print = pprint = oprint = print
+
+    def inspect(*args, **kwargs):
+        warnings.warn(f"{__name__}.inspect() requires rich.")
+
 else:
     oprint = print  # In-case plain old behavior is needed
 

From 832d72fe4e23ac48f8fc6f6cf23132a559a4cfc0 Mon Sep 17 00:00:00 2001
From: Verdi March <verdimrc@users.noreply.github.com>
Date: Thu, 24 Feb 2022 12:26:16 +0800
Subject: [PATCH 6/6] skeleton.ipynb: renamed {rprint => pprint}

---
 {{cookiecutter.repo_name}}/notebooks/skeleton.ipynb | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/{{cookiecutter.repo_name}}/notebooks/skeleton.ipynb b/{{cookiecutter.repo_name}}/notebooks/skeleton.ipynb
index 4ec4031..d825dc2 100644
--- a/{{cookiecutter.repo_name}}/notebooks/skeleton.ipynb
+++ b/{{cookiecutter.repo_name}}/notebooks/skeleton.ipynb
@@ -31,7 +31,7 @@
     "\n",
     "# Make sure my_nb_path is imported first (and when isort is used, it needs to be told).\n",
     "import my_nb_path  # isort: skip\n",
-    "from my_nb_color import print, rprint"
+    "from my_nb_color import print, pprint, inspect"
    ]
   },
   {
@@ -205,7 +205,7 @@
    "source": [
     "d = {\"A\" * 200, \"B\" * 200}\n",
     "print(\"Colored:\", d)\n",
-    "rprint(\"Colored and wrapped:\", d)\n",
+    "pprint(\"Colored and wrapped:\", d)\n",
     "display(d)\n",
     "\n",
     "for f in (logger.debug, logger.info, logger.success, logger.error):\n",
