Commits: 1

Allow an optional caption for snippet observations

It's often useful to explain what the snippet is about. A dedicated caption should be better than sending a text observation right before the snippet. In HTML or other rich export formats it can be rendered in a pretty and semantically correct way, i.e. with structural connection to the snippet.

index 96dc34a..eee3d46 100644
--- a/samples/basic.py
+++ b/samples/basic.py
@@ -58,8 +58,11 @@ def step_implementation_06(word: str, reverse: str, **kwargs):
=@step("The following tables map words to their lengths")
=def step_implementation_07(**kwargs):
=    for table in kwargs["tables"]:
-        send_text(f"Received a table with {len(table)} x {len(table[0])} cells")
-        send_snippet("json", json.dumps(table, indent=2))
+        send_snippet(
+            "json",
+            json.dumps(table, indent=2),
+            caption = f"A {len(table)} x {len(table[0])} table"
+        )
=        
=        # Skip the first row - it's a heading
=        for [word, length] in table[1:]:
index 86d94af..4787ef4 100644
--- a/spec/basic-usage.md
+++ b/spec/basic-usage.md
@@ -208,7 +208,7 @@ When a directory is given as the last argument, load all documents inside (recur
=          code blocks: 1, tables: 2
=          source: samples/basic.md:59-93
=
-          > Received a table with 6 x 2 cells
+          A 6 x 2 table:
=
=          \``` json
=          [
@@ -254,8 +254,7 @@ When a directory is given as the last argument, load all documents inside (recur
=          > Is 'minx' 4 characters long? I think it's 4.
=
=
-          > Received a table with 4 x 2 cells
-
+          A 4 x 2 table:
=
=          \``` json
=          [
index cf3096f..8e8f0db 100644
--- a/spec/tbb.py
+++ b/spec/tbb.py
@@ -193,14 +193,15 @@ def send_text(content):
=        "content": content,
=    })
=
-def send_snippet(language, content, meta = ""):
+def send_snippet(language, content, meta = "", caption = None):
=    """ Send a snippet of code to be displayed in a TBB report
=    """
=    send({
=        "type": "Snippet",
=        "language": language,
=        "content": content,
-        "meta": meta
+        "meta": meta,
+        "caption": caption,
=    })
=    
=
index 57da103..6bc03b3 100644
--- a/src/report.rs
+++ b/src/report.rs
@@ -177,11 +177,13 @@ impl<'a> ScenarioReport<'a> {
=                        language,
=                        meta,
=                        content,
+                        caption,
=                    } => {
=                        observations.push(Observation::Snippet {
=                            language,
=                            meta,
=                            content,
+                            caption,
=                        });
=                    }
=                    InterpreterMessage::Link { url, label } => {
@@ -292,6 +294,7 @@ pub enum Observation {
=        language: String,
=        meta: String,
=        content: String,
+        caption: Option<String>,
=    },
=    // Image,
=    // Audio,
@@ -310,7 +313,12 @@ impl Display for Observation {
=                language,
=                meta,
=                content,
+                caption,
=            } => {
+                if let Some(caption) = caption {
+                    writeln!(f, "{caption}:")?;
+                    writeln!(f, "")?;
+                }
=                writeln!(f, "``` {language} {meta}")?;
=                writeln!(f, "{content}")?;
=                writeln!(f, "```")
@@ -508,6 +516,7 @@ pub enum InterpreterMessage {
=        language: String,
=        meta: String,
=        content: String,
+        caption: Option<String>,
=    },
=    Link {
=        url: String,