Skip to content

Commit 984a0c0

Browse files
committed
fix: add structured rendering of dep resolution
1 parent 2e35363 commit 984a0c0

File tree

4 files changed

+261
-74
lines changed

4 files changed

+261
-74
lines changed

src/main/java/dev/jbang/jupyter/JBangCellMagic.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import org.dflib.jjava.jupyter.kernel.magic.CellMagic;
44
import org.dflib.jjava.kernel.JavaKernel;
55

6+
import dev.jbang.jupyter.JBangHelper.JBangInfo;
7+
68
import java.io.File;
79
import java.util.List;
810

@@ -15,7 +17,8 @@ public class JBangCellMagic implements CellMagic<Void, JavaKernel> {
1517

1618
public Void eval(JavaKernel kernel, List<String> args, String body) throws Exception {
1719
try {
18-
List<String> resolvedDependencies = JBangHelper.getJBangResolvedDependencies("-", body,false);
20+
JBangInfo jbangInfo = JBangHelper.getJBangResolvedDependencies("-", body,false);
21+
List<String> resolvedDependencies = jbangInfo.resolvedDependencies;
1922
kernel.addToClasspath(String.join(File.pathSeparator,resolvedDependencies));
2023
// let the kernel evaluate the body after the dependencies are resolved
2124
// TODO: this is not right way as extensions can't give callback

src/main/java/dev/jbang/jupyter/JBangHelper.java

Lines changed: 154 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@
1010
import java.nio.file.Files;
1111
import java.nio.file.Path;
1212
import java.util.ArrayList;
13+
import java.util.HashSet;
1314
import java.util.List;
15+
import java.util.Set;
1416
import java.util.logging.Logger;
1517
import java.util.stream.Collectors;
1618
import java.util.stream.StreamSupport;
1719

20+
import org.dflib.jjava.jupyter.kernel.display.Renderer;
21+
import org.dflib.jjava.jupyter.kernel.display.mime.MIMEType;
22+
1823
public class JBangHelper {
1924

2025
final static Logger logger = Logger.getLogger(JBangHelper.class.getName());
@@ -24,8 +29,8 @@ public class JBangHelper {
2429
*
2530
* Note: on windows it will look for jbang.cmd not jbang which is a shell script.
2631
*
27-
* 1. $JBANG_HOME/bin/jbang
28-
* 2. jbang in $PATH
32+
* 1. /bin/jbang
33+
* 2. jbang in
2934
* 3. ~/.jbang/bin/jbang
3035
*
3136
* If none of the above are found, it will return null.
@@ -37,7 +42,7 @@ public static String findJBangExecutable() throws IOException {
3742
boolean isWindows = System.getProperty("os.name").toLowerCase().contains("windows");
3843
String jbangExecutable = isWindows ? "jbang.cmd" : "jbang";
3944

40-
// 1. $JBANG_HOME/bin/jbang
45+
// 1. /bin/jbang
4146
String jbangHome = System.getenv("JBANG_HOME");
4247
if (jbangHome != null) {
4348
var homePath = Path.of(jbangHome).resolve("bin/" + jbangExecutable);
@@ -46,7 +51,7 @@ public static String findJBangExecutable() throws IOException {
4651
}
4752
}
4853

49-
// 2. jbang in $PATH
54+
// 2. jbang in
5055
String pathEnv = System.getenv("PATH");
5156
if (pathEnv != null) {
5257
for (String dir : pathEnv.split(File.pathSeparator)) {
@@ -67,7 +72,143 @@ public static String findJBangExecutable() throws IOException {
6772
return null;
6873
}
6974

70-
public static List<String> getJBangResolvedDependencies(String scriptRef, String body, boolean inclAppJar) throws IOException {
75+
public static class JBangInfo {
76+
List<String> resolvedDependencies;
77+
List<String> dependencies;
78+
List<String> newResolvedDependencies;
79+
80+
public JBangInfo(List<String> resolvedDependencies, List<String> dependencies) {
81+
this.resolvedDependencies = resolvedDependencies;
82+
this.dependencies = dependencies;
83+
}
84+
85+
public void resolve(Set<String> existingClasspath) {
86+
this.newResolvedDependencies = getNewResolvedDependencies(existingClasspath);
87+
}
88+
89+
public List<String> getNewResolvedDependencies(Set<String> existingClasspath) {
90+
return resolvedDependencies.stream()
91+
.filter(dependency -> !existingClasspath.contains(dependency))
92+
.collect(Collectors.toList());
93+
}
94+
95+
public List<String> getDependencies() {
96+
return dependencies;
97+
}
98+
public List<String> getResolvedDependencies() {
99+
return resolvedDependencies;
100+
}
101+
public List<String> getNewResolvedDependencies() {
102+
return newResolvedDependencies;
103+
}
104+
105+
public String toString() {
106+
StringBuilder sb = new StringBuilder("JBangInfo{");
107+
sb.append("dependencies=").append(dependencies.size());
108+
sb.append(",resolvedDependencies=").append(resolvedDependencies.size());
109+
if (newResolvedDependencies != null) {
110+
sb.append(",newResolvedDependencies=").append(newResolvedDependencies.size());
111+
}
112+
sb.append('}');
113+
return sb.toString();
114+
}
115+
116+
public static void registerAllRenderers(Renderer renderer) {
117+
renderer
118+
.createRegistration(dev.jbang.jupyter.JBangHelper.JBangInfo.class)
119+
.preferring(MIMEType.TEXT_HTML)
120+
.register((info, ctx) -> {
121+
ctx.renderIfRequested(MIMEType.TEXT_HTML, () -> {
122+
StringBuilder html = new StringBuilder();
123+
124+
// Create set of new dependencies for quick lookup
125+
Set<String> newDepsSet = new HashSet<>();
126+
if (info.getNewResolvedDependencies() != null) {
127+
newDepsSet.addAll(info.getNewResolvedDependencies());
128+
}
129+
130+
// Summary section
131+
int totalRequested = (info.getDependencies() != null) ? info.getDependencies().size() : 0;
132+
int totalResolved = (info.getResolvedDependencies() != null) ? info.getResolvedDependencies().size() : 0;
133+
int totalNew = (info.getNewResolvedDependencies() != null) ? info.getNewResolvedDependencies().size() : 0;
134+
135+
// Styles
136+
html.append("<style>\n");
137+
html.append(".jbang-compact { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; font-size: 12px; margin: 4px 0; }\n");
138+
html.append(".jbang-inline { display: inline-block; margin: 0; padding: 0; vertical-align: top; }\n");
139+
html.append(".jbang-inline summary { display: inline; cursor: pointer; user-select: none; padding: 2px 6px; border-radius: 3px; transition: background 0.15s; }\n");
140+
html.append(".jbang-inline summary:hover { background: #f1f3f5; }\n");
141+
html.append(".jbang-inline-content { margin-top: 6px; padding: 8px; background: #f8f9fa; border-radius: 4px; border-left: 2px solid #667eea; }\n");
142+
html.append(".jbang-inline-new .jbang-inline-content { border-left-color: #10b981; }\n");
143+
html.append(".jbang-inline-coord .jbang-inline-content { border-left-color: #764ba2; }\n");
144+
html.append(".jbang-list { list-style: none; padding: 0; margin: 0; font-size: 11px; }\n");
145+
html.append(".jbang-list-item { padding: 4px 6px; margin: 2px 0; background: white; border-radius: 2px; border-left: 2px solid #667eea; font-family: 'Monaco', 'Menlo', monospace; color: #2d3748; word-break: break-all; cursor: help; }\n");
146+
html.append(".jbang-list-item:hover { background: #f8f9fa; }\n");
147+
html.append(".jbang-list-item-new { border-left-color: #10b981; }\n");
148+
html.append(".jbang-list-item-old { opacity: 0.4; border-left-color: #adb5bd; }\n");
149+
html.append(".jbang-list-item-old:hover { opacity: 0.6; }\n");
150+
html.append(".jbang-list-item-coord { border-left-color: #764ba2; cursor: default; }\n");
151+
html.append("</style>\n");
152+
153+
if (totalRequested > 0 || totalResolved > 0) {
154+
html.append("<div class='jbang-compact'>\n");
155+
html.append(" 🚀 JBang: ");
156+
157+
// Requested dependencies - always show, inline expandable if > 0
158+
if (info.getDependencies() != null && !info.getDependencies().isEmpty()) {
159+
html.append("<details class='jbang-inline jbang-inline-coord'><summary title='Requested Maven dependencies (click to expand)'>📦 <strong>").append(totalRequested).append("</strong></summary>");
160+
html.append("<div class='jbang-inline-content'><ul class='jbang-list'>");
161+
for (String dep : info.getDependencies()) {
162+
html.append("<li class='jbang-list-item jbang-list-item-coord'>").append(dep).append("</li>");
163+
}
164+
html.append("</ul></div></details>");
165+
} else {
166+
html.append("<span title='Requested Maven dependencies'>📦 <strong>").append(totalRequested).append("</strong></span>");
167+
}
168+
169+
html.append(" | ");
170+
171+
// New dependencies - always show, inline expandable if > 0
172+
if (info.getNewResolvedDependencies() != null && !info.getNewResolvedDependencies().isEmpty()) {
173+
html.append("<details class='jbang-inline jbang-inline-new'><summary title='Newly added to classpath (click to expand)'>✨ <strong>").append(totalNew).append("</strong></summary>");
174+
html.append("<div class='jbang-inline-content'><ul class='jbang-list'>");
175+
for (String dep : info.getNewResolvedDependencies()) {
176+
String fileName = dep.substring(dep.lastIndexOf('/') + 1);
177+
html.append("<li class='jbang-list-item jbang-list-item-new' title='").append(dep).append("'>").append(fileName).append("</li>");
178+
}
179+
html.append("</ul></div></details>");
180+
} else {
181+
html.append("<span title='Newly added to classpath'>✨ <strong>").append(totalNew).append("</strong></span>");
182+
}
183+
184+
html.append(" | ");
185+
186+
// All resolved dependencies - always show, inline expandable if > 0
187+
if (info.getResolvedDependencies() != null && !info.getResolvedDependencies().isEmpty()) {
188+
html.append("<details class='jbang-inline'><summary title='All resolved JARs (click to expand, grayed = cached)'>📚 <strong>").append(totalResolved).append("</strong></summary>");
189+
html.append("<div class='jbang-inline-content'><ul class='jbang-list'>");
190+
for (String dep : info.getResolvedDependencies()) {
191+
String fileName = dep.substring(dep.lastIndexOf('/') + 1);
192+
boolean isNew = newDepsSet.contains(dep);
193+
String itemClass = isNew ? "jbang-list-item jbang-list-item-new" : "jbang-list-item jbang-list-item-old";
194+
html.append("<li class='").append(itemClass).append("' title='").append(dep).append("'>").append(fileName).append("</li>");
195+
}
196+
html.append("</ul></div></details>");
197+
} else {
198+
html.append("<span title='All resolved JARs'>📚 <strong>").append(totalResolved).append("</strong></span>");
199+
}
200+
201+
html.append("</div>\n");
202+
} else {
203+
html.append("<div class='jbang-compact'>🚀 JBang: <em>No dependencies</em></div>\n");
204+
}
205+
206+
return html.toString();
207+
});
208+
});
209+
}
210+
}
211+
public static JBangInfo getJBangResolvedDependencies(String scriptRef, String body, boolean inclAppJar) throws IOException {
71212
try {
72213
String jbangExecutable = findJBangExecutable();
73214
if (jbangExecutable == null) {
@@ -109,11 +250,18 @@ public static List<String> getJBangResolvedDependencies(String scriptRef, String
109250
.collect(Collectors.toList());
110251
}
111252

253+
List<String> dependencies = new ArrayList<>();
254+
if (json.has("dependencies") && json.get("dependencies").isJsonArray()) {
255+
dependencies = StreamSupport.stream(json.getAsJsonArray("dependencies").spliterator(), false)
256+
.map(JsonElement::getAsString)
257+
.collect(Collectors.toList());
258+
}
259+
112260
if (inclAppJar) {
113261
resolvedDependencies.add(json.get("applicationJar").getAsString());
114262
}
115263

116-
return resolvedDependencies;
264+
return new JBangInfo(resolvedDependencies, dependencies);
117265

118266

119267
} catch (Exception e) {

0 commit comments

Comments
 (0)