Repository: Textualize/rich
Pull request #2537 (2022-09-21 20:56:03)

Add support for specifying (persistent) id for SVG exports
As part of Airflow's CI we save SVG images of our command's help output and commit them to git.

Without this change a single character change in the help text results in every single class and id in the SVG changing, which makes the diffs unreadable.

This adds an option for us to be able to specify a fixed prefix so that our git diffs are minimized.

(I also tided up the help docstring for export_svg and save_svg which didn't match signature any more)


willmcgugan: Are you aware of the reasoning behind the unique id? It is to allow embedding of SVGs without conflicting styles. If you make the unique id non unique you will not be able to embed more than one SVG.
ashb: Yes, we can give each image it's own unique (but stable name) based on the sub command name (we use rich-click). I can expand on the docs to make point to others?
ashb: Whoops, that'll teach me for making changes in PR suggestion. Fixing...
ashb: Fixing tests now. One moment please
willmcgugan: Thanks, probably a result of the merge.

Patch:
diff --git a/rich/console.py b/rich/console.py
index 33e9d422..36537216 100644
--- a/rich/console.py
+++ b/rich/console.py
@@ -2262,12 +2262,12 @@ class Console:
         clear: bool = True,
         code_format: str = CONSOLE_SVG_FORMAT,
         font_aspect_ratio: float = 0.61,
+        unique_id: Optional[str] = None,
     ) -> str:
         """
         Generate an SVG from the console contents (requires record=True in Console constructor).
 
         Args:
-            path (str): The path to write the SVG to.
             title (str, optional): The title of the tab in the output image
             theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal
             clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``
@@ -2277,6 +2277,8 @@ class Console:
             font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format``
                 string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font).
                 If you aren't specifying a different font inside ``code_format``, you probably don't need this.
+            unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node
+                ids). If not set, this defaults to a computed value based on the recorded content.
         """
 
         from rich.cells import cell_len
@@ -2372,14 +2374,16 @@ class Console:
             if clear:
                 self._record_buffer.clear()
 
-        unique_id = "terminal-" + str(
-            zlib.adler32(
-                ("".join(repr(segment) for segment in segments)).encode(
-                    "utf-8", "ignore"
+        if unique_id is None:
+            unique_id = "terminal-" + str(
+                zlib.adler32(
+                    ("".join(repr(segment) for segment in segments)).encode(
+                        "utf-8",
+                        "ignore",
+                    )
+                    + title.encode("utf-8", "ignore")
                 )
-                + title.encode("utf-8", "ignore")
             )
-        )
         y = 0
         for y, line in enumerate(Segment.split_and_crop_lines(segments, length=width)):
             x = 0
@@ -2510,6 +2514,7 @@ class Console:
         clear: bool = True,
         code_format: str = CONSOLE_SVG_FORMAT,
         font_aspect_ratio: float = 0.61,
+        unique_id: Optional[str] = None,
     ) -> None:
         """Generate an SVG file from the console contents (requires record=True in Console constructor).
 
@@ -2524,6 +2529,8 @@ class Console:
             font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format``
                 string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font).
                 If you aren't specifying a different font inside ``code_format``, you probably don't need this.
+            unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node
+                ids). If not set, this defaults to a computed value based on the recorded content.
         """
         svg = self.export_svg(
             title=title,
@@ -2531,6 +2538,7 @@ class Console:
             clear=clear,
             code_format=code_format,
             font_aspect_ratio=font_aspect_ratio,
+            unique_id=unique_id,
         )
         with open(path, "wt", encoding="utf-8") as write_file:
             write_file.write(svg)


Test patch:
diff --git a/tests/test_console.py b/tests/test_console.py
index 0b46272f..e1aaaefd 100644
--- a/tests/test_console.py
+++ b/tests/test_console.py
@@ -554,6 +554,18 @@ def test_export_svg():
     assert svg == EXPECTED_SVG
 
 
+def test_export_svg_specified_unique_id():
+    expected_svg = EXPECTED_SVG.replace("terminal-3526644552", "given-id")
+    console = Console(record=True, width=100)
+    console.print(
+        "[b red on blue reverse]foo[/] [blink][link=https://example.org]Click[/link]"
+    )
+    svg = console.export_svg(unique_id="given-id")
+    print(repr(svg))
+
+    assert svg == expected_svg
+
+
 def test_save_svg():
     console = Console(record=True, width=100)
     console.print(