diff --git a/labs/code-graph/README.md b/labs/code-graph/README.md index 4f0a399..b1d70a2 100644 --- a/labs/code-graph/README.md +++ b/labs/code-graph/README.md @@ -125,10 +125,10 @@ Measured on x86_64-linux with `nix path-info -Sh` and `stat`, nixpkgs `6510408d` | | Size | |---|---| -| `packages.default` binary (glibc, stripped) | 15.8 MiB (16,591,008 bytes) | +| `packages.default` binary (glibc, stripped) | 15.9 MiB (16,636,064 bytes) | | `packages.default` closure | 62.8 MiB | -| `packages.static` (musl) binary and closure | 15.9 MiB (static, no other file) | -| `packages.static` with `xz -9e` | 1.1 MiB (a measurement only: see the xz warning in [`ptbr-pdf`](../ptbr-pdf/README.md#benchmark-from-pandoc-to-rust)) | +| `packages.static` (musl) binary and closure | 16.0 MiB (16,734,160 bytes; static, no other file) | +| `packages.static` with `xz -9e` | 1.1 MiB (1,192,652 bytes) (a measurement only: see the xz warning in [`ptbr-pdf`](../ptbr-pdf/README.md#benchmark-from-pandoc-to-rust)) | | nixpkgs `graphify` 0.9.74, closure | 579.7 MiB | The 18.3 MB ceiling in `checks.size` is the measured binary plus 10%. Most of the size is the Scala, diff --git a/labs/code-graph/assets/viewer.js b/labs/code-graph/assets/viewer.js index db75167..d4b9f91 100644 --- a/labs/code-graph/assets/viewer.js +++ b/labs/code-graph/assets/viewer.js @@ -49,6 +49,7 @@ var n = D.n[i]; row(n[0], "t"); row(n[1] + (n[2] ? " " + n[2] : "")); + if (n[7]) row("label: " + n[7]); row("community: " + n[4]); if (n[5]) { var a = document.createElement("a"); diff --git a/labs/code-graph/tools/src/graph.rs b/labs/code-graph/tools/src/graph.rs index 13991ac..da0c946 100644 --- a/labs/code-graph/tools/src/graph.rs +++ b/labs/code-graph/tools/src/graph.rs @@ -13,6 +13,8 @@ pub struct Node { pub cname: String, /// the short name of the linked repository this node stands for (`--link`), else empty pub repo: String, + /// a linked node's own label, its `label` being the PATH it was found by (`--link`) + pub orig: String, /// where that repository's graph.html is, focused on the node (`--link`), else empty pub href: String, } @@ -69,6 +71,9 @@ impl Graph { m.insert("external".into(), v); m.insert("type".into(), json!("external")); } + if !n.orig.is_empty() { + m.insert("linked_label".into(), json!(n.orig)); + } if !n.href.is_empty() { m.insert("href".into(), json!(n.href)); } @@ -143,6 +148,7 @@ impl Graph { .unwrap_or("") .to_string(), href: s(n, "href"), + orig: s(n, "linked_label"), community: n.get("community").and_then(Value::as_u64).unwrap_or(0) as usize, cname, }); diff --git a/labs/code-graph/tools/src/html.rs b/labs/code-graph/tools/src/html.rs index d89f786..dfe0baf 100644 --- a/labs/code-graph/tools/src/html.rs +++ b/labs/code-graph/tools/src/html.rs @@ -16,6 +16,7 @@ const RELS: &[&str] = &[ "inherits", "implements", "references", + "depends_on", ]; fn esc(s: &str) -> String { @@ -170,12 +171,22 @@ pub fn render(g: &Graph, title: &str) -> String { } o += ""; // labels for the best-connected nodes, skipping any that would overlap one already placed + // always labelled: the nodes of a linked repository and the files that point at them + let mut always: Vec = g.nodes.iter().map(|x| !x.repo.is_empty()).collect(); + for e in &g.edges { + if !g.nodes[e.t].repo.is_empty() { + always[e.s] = true; + } + } let mut order: Vec = (0..n) .filter(|&i| !g.nodes[i].external || !g.nodes[i].repo.is_empty()) .collect(); - order.sort_by_key(|&i| (g.nodes[i].repo.is_empty(), std::cmp::Reverse(deg[i]), i)); + order.sort_by_key(|&i| (!always[i], std::cmp::Reverse(deg[i]), i)); let mut boxes: Vec<(f64, f64, f64, f64)> = Vec::new(); - for i in order.into_iter().take(400) { + for i in order + .into_iter() + .take(400 + always.iter().filter(|&&a| a).count()) + { let (x, y) = (pos[i].0 + 8.0, pos[i].1 + 4.0); let b = ( x, @@ -183,10 +194,11 @@ pub fn render(g: &Graph, title: &str) -> String { x + 7.6 * g.nodes[i].label.chars().count() as f64, y + 3.0, ); - if boxes.len() >= 70 - || boxes - .iter() - .any(|q| b.0 < q.2 && q.0 < b.2 && b.1 < q.3 && q.1 < b.3) + if !always[i] + && (boxes.len() >= 70 + || boxes + .iter() + .any(|q| b.0 < q.2 && q.0 < b.2 && b.1 < q.3 && q.1 < b.3)) { continue; } @@ -232,12 +244,12 @@ pub fn render(g: &Graph, title: &str) -> String { ); } o += "

Relations

    "; - for r in RELS { + for r in RELS.iter().filter(|r| g.edges.iter().any(|e| e.rel == **r)) { let _ = write!(o, "
  • {r}
  • "); } o += "
\n\n"; let data = json!({ - "n": g.nodes.iter().map(|x| json!([x.label, x.file, if x.line > 0 { format!("L{}", x.line) } else { String::new() }, x.community, x.cname, x.href, x.id])).collect::>(), + "n": g.nodes.iter().map(|x| json!([x.label, x.file, if x.line > 0 { format!("L{}", x.line) } else { String::new() }, x.community, x.cname, x.href, x.id, x.orig])).collect::>(), "e": g.edges.iter().map(|e| json!([e.s, e.t, e.rel])).collect::>(), "v": [0, 0, w, h], }); @@ -266,8 +278,8 @@ i.s0,.c0{fill:var(--c0);background:var(--c0)}i.s1,.c1{fill:var(--c1);background: i.s4,.c4{fill:var(--c4);background:var(--c4)}i.s5,.c5{fill:var(--c5);background:var(--c5)}i.s6,.c6{fill:var(--c6);background:var(--c6)}i.s7,.c7{fill:var(--c7);background:var(--c7)} i.s8,.c8{fill:var(--c8);background:var(--c8)}i.s9,.c9{fill:var(--c9);background:var(--c9)}i.s10,.c10{fill:var(--c10);background:var(--c10)}i.s11,.c11{fill:var(--c11);background:var(--c11)} i{display:inline-block;width:10px;height:10px;border-radius:50%;flex:none}svg circle{background:none;stroke:var(--bg);stroke-width:1}svg circle.x{opacity:.55}svg circle.r{fill:var(--bg);stroke-width:2.5;stroke-dasharray:3 2}i.r{background:none;border:2px dashed var(--mut)} -.e{stroke-width:1;stroke:var(--mut);opacity:.35}.e.contains,.e.method,.e.defines{opacity:.18}.e.calls{stroke:var(--c1);opacity:.6}.e.imports_from,.e.imports{stroke:var(--c0);opacity:.5}.e.inherits,.e.implements{stroke:var(--c3);opacity:.7}.e.references{stroke:var(--c2);opacity:.4} -i.l{width:16px;height:3px;border-radius:0;background:var(--mut)}i.l.calls{background:var(--c1)}i.l.imports_from,i.l.imports{background:var(--c0)}i.l.inherits,i.l.implements{background:var(--c3)}i.l.references{background:var(--c2)} +.e{stroke-width:1;stroke:var(--mut);opacity:.35}.e.contains,.e.method,.e.defines{opacity:.18}.e.calls{stroke:var(--c1);opacity:.6}.e.imports_from,.e.imports{stroke:var(--c0);opacity:.5}.e.inherits,.e.implements{stroke:var(--c3);opacity:.7}.e.references{stroke:var(--c2);opacity:.4}.e.depends_on{stroke:var(--c4);opacity:.9;stroke-width:1.5;stroke-dasharray:4 3} +i.l{width:16px;height:3px;border-radius:0;background:var(--mut)}i.l.calls{background:var(--c1)}i.l.imports_from,i.l.imports{background:var(--c0)}i.l.inherits,i.l.implements{background:var(--c3)}i.l.references{background:var(--c2)}i.l.depends_on{background:var(--c4)} text{font-size:13px;fill:var(--fg);paint-order:stroke;stroke:var(--bg);stroke-width:3px;pointer-events:none} svg.f circle{opacity:.1}svg.f .e{opacity:.03}svg.f circle.on{opacity:1}svg.f .e.on{opacity:.9;stroke-width:1.6}svg.f #lab{display:none}svg circle.pick{stroke:var(--fg);stroke-width:2} @media (max-width:760px){main{flex-direction:column}aside{width:auto;max-height:40vh;border-left:0;border-top:1px solid var(--line)}} diff --git a/labs/code-graph/tools/src/link.rs b/labs/code-graph/tools/src/link.rs index 21c1bbf..35dadaa 100644 --- a/labs/code-graph/tools/src/link.rs +++ b/labs/code-graph/tools/src/link.rs @@ -131,11 +131,17 @@ fn enc(s: &str) -> String { pub fn apply(g: &mut Graph, files: &[(String, String)], links: &[Link]) { // (referencing file, link, target node in the linked graph) -> first line let mut found: BTreeMap<(&str, usize, usize), u32> = BTreeMap::new(); + // the first PATH, in sorted order, that resolved to each node: its label + let mut via: BTreeMap<(usize, usize), String> = BTreeMap::new(); for (path, text) in files { for (li, l) in links.iter().enumerate() { for (p, line) in refs(&l.name, text) { if let Some(t) = resolve(&l.graph, &p) { found.entry((path.as_str(), li, t)).or_insert(line); + let v = via.entry((li, t)).or_insert_with(|| p.clone()); + if p < *v { + *v = p; + } } } } @@ -178,7 +184,8 @@ pub fn apply(g: &mut Graph, files: &[(String, String)], links: &[Link]) { let o = &l.graph.nodes[t]; g.nodes.push(Node { id: format!("{}:{}", l.name, o.id), - label: format!("{}: {}", l.name, o.label), + label: format!("{}: {}", l.name, via[&(li, t)]), + orig: o.label.clone(), file: format!("{}:{}", l.name, o.file), line: o.line, external: true, diff --git a/labs/code-graph/tools/tests/cli.rs b/labs/code-graph/tools/tests/cli.rs index a80aea5..94bea78 100644 --- a/labs/code-graph/tools/tests/cli.rs +++ b/labs/code-graph/tools/tests/cli.rs @@ -297,7 +297,8 @@ fn link_adds_the_referenced_nodes_of_another_repository() { let g = linked(&a, &out, &spec); let x = node(&g, "b:labs_x_flake"); assert_eq!(x["external"], "b"); - assert_eq!(x["label"], "b: flake.nix"); + assert_eq!(x["label"], "b: labs/x"); + assert_eq!(x["linked_label"], "flake.nix"); assert_eq!(x["href"], "b.html#node=labs_x_flake"); assert_eq!(x["source_file"], "b:labs/x/flake.nix"); assert_eq!(x["community_name"], "b"); @@ -324,6 +325,8 @@ fn link_adds_the_referenced_nodes_of_another_repository() { // the page: one relative link per external node, no network, no href attribute let h = std::fs::read_to_string(out.join("graph.html")).unwrap(); assert!(h.contains("b.html#node=labs_x_flake") && h.contains("external: b")); + // the relations legend lists what the graph has + assert!(h.contains("") && !h.contains("")); for bad in [ "http://", "https://", "src=", "href=", "@import", "url(", "