]> git.scottworley.com Git - tablify/blobdiff - src/lib.rs
Leftovers column
[tablify] / src / lib.rs
index 47e1d3a9eb1fa10fac60a16cf66153b55d5d725f..bad71d0b2c2116d9134495c08f8316b84ffd4a7b 100644 (file)
@@ -187,36 +187,39 @@ fn column_order(rows: &[Row]) -> Vec<String> {
         .collect()
 }
 
         .collect()
 }
 
-fn render_instance(instance: &Option<String>) -> HTML {
+fn render_one_instance(instance: &Option<String>) -> HTML {
     match instance {
         None => HTML::from("✓"),
         Some(instance) => HTML::escape(instance.as_ref()),
     }
 }
 
     match instance {
         None => HTML::from("✓"),
         Some(instance) => HTML::escape(instance.as_ref()),
     }
 }
 
-fn render_cell(col: &str, row: &mut Row) -> HTML {
-    let row_label = HTML::escape(row.label.as_ref());
-    let col_label = HTML::escape(col);
-    let instances: Option<&Vec<Option<String>>> = row.entries.get(col);
-    let class = HTML::from(if instances.is_none() { "" } else { "yes" });
-    let all_empty = instances
-        .iter()
-        .flat_map(|is| is.iter())
-        .all(Option::is_none);
-    let contents = if instances.is_none() || (all_empty && instances.unwrap().len() == 1) {
+fn render_instances(instances: &[Option<String>]) -> HTML {
+    let all_empty = instances.iter().all(Option::is_none);
+    if all_empty && instances.len() == 1 {
         HTML::from("")
     } else if all_empty {
         HTML::from("")
     } else if all_empty {
-        HTML(format!("{}", instances.unwrap().len()))
+        HTML(format!("{}", instances.len()))
     } else {
         HTML(
             instances
     } else {
         HTML(
             instances
-                .unwrap()
                 .iter()
                 .iter()
-                .map(render_instance)
+                .map(render_one_instance)
                 .map(|html| html.0) // Waiting for slice_concat_trait to stabilize
                 .collect::<Vec<_>>()
                 .join(" "),
         )
                 .map(|html| html.0) // Waiting for slice_concat_trait to stabilize
                 .collect::<Vec<_>>()
                 .join(" "),
         )
+    }
+}
+
+fn render_cell(col: &str, row: &mut Row) -> HTML {
+    let row_label = HTML::escape(row.label.as_ref());
+    let col_label = HTML::escape(col);
+    let instances: Option<&Vec<Option<String>>> = row.entries.get(col);
+    let class = HTML::from(if instances.is_none() { "" } else { "yes" });
+    let contents = match instances {
+        None => HTML::from(""),
+        Some(is) => render_instances(is),
     };
     row.entries.remove(col);
     HTML(format!(
     };
     row.entries.remove(col);
     HTML(format!(
@@ -224,14 +227,38 @@ fn render_cell(col: &str, row: &mut Row) -> HTML {
     ))
 }
 
     ))
 }
 
+fn render_leftover(notcol: &str, instances: &[Option<String>]) -> HTML {
+    let label = HTML::escape(notcol);
+    let rest = render_instances(instances);
+    if rest == HTML::from("") {
+        HTML(format!("{label}"))
+    } else {
+        HTML(format!("{label}: {rest}"))
+    }
+}
+
+fn render_all_leftovers(row: &Row) -> HTML {
+    let mut order: Vec<_> = row.entries.keys().collect();
+    order.sort_unstable();
+    HTML(
+        order
+            .into_iter()
+            .map(|notcol| render_leftover(notcol, row.entries.get(notcol).expect("Key vanished?!")))
+            .map(|html| html.0) // Waiting for slice_concat_trait to stabilize
+            .collect::<Vec<_>>()
+            .join(", "),
+    )
+}
+
 fn render_row(columns: &[String], row: &mut Row) -> HTML {
     let row_label = HTML::escape(row.label.as_ref());
 fn render_row(columns: &[String], row: &mut Row) -> HTML {
     let row_label = HTML::escape(row.label.as_ref());
+    let cells = columns
+        .iter()
+        .map(|col| render_cell(col, row))
+        .collect::<HTML>();
+    let leftovers = render_all_leftovers(row);
     HTML(format!(
     HTML(format!(
-        "<tr><th id=\"{row_label}\">{row_label}</th>{}</tr>\n",
-        &columns
-            .iter()
-            .map(|col| render_cell(col, row))
-            .collect::<HTML>()
+        "<tr><th id=\"{row_label}\">{row_label}</th>{cells}<td onmouseover=\"highlight('{row_label}')\" onmouseout=\"clear_highlight('{row_label}')\">{leftovers}</td></tr>\n"
     ))
 }
 
     ))
 }
 
@@ -552,6 +579,37 @@ mod tests {
         assert_eq!(r.entries.len(), 0);
     }
 
         assert_eq!(r.entries.len(), 0);
     }
 
+    #[test]
+    fn test_render_leftovers() {
+        assert_eq!(
+            render_all_leftovers(&Row {
+                label: "nope".to_owned(),
+                entries: HashMap::from([("foo".to_owned(), vec![None])]),
+            }),
+            HTML::from("foo")
+        );
+        assert_eq!(
+            render_all_leftovers(&Row {
+                label: "nope".to_owned(),
+                entries: HashMap::from([
+                    ("foo".to_owned(), vec![None]),
+                    ("bar".to_owned(), vec![None])
+                ]),
+            }),
+            HTML::from("bar, foo")
+        );
+        assert_eq!(
+            render_all_leftovers(&Row {
+                label: "nope".to_owned(),
+                entries: HashMap::from([
+                    ("foo".to_owned(), vec![None]),
+                    ("bar".to_owned(), vec![None, None])
+                ]),
+            }),
+            HTML::from("bar: 2, foo")
+        );
+    }
+
     #[test]
     fn test_render_row() {
         assert_eq!(
     #[test]
     fn test_render_row() {
         assert_eq!(
@@ -563,7 +621,7 @@ mod tests {
                 }
             ),
             HTML::from(
                 }
             ),
             HTML::from(
-                r#"<tr><th id="nope">nope</th><td class="" onmouseover="h2('nope','foo')" onmouseout="ch2('nope','foo')"></td></tr>
+                r#"<tr><th id="nope">nope</th><td class="" onmouseover="h2('nope','foo')" onmouseout="ch2('nope','foo')"></td><td onmouseover="highlight('nope')" onmouseout="clear_highlight('nope')">bar</td></tr>
 "#
             )
         );
 "#
             )
         );