gnodet-bot commented on code in PR #27501:
URL: https://github.com/apache/camel/pull/27501#discussion_r4207068665
##########
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CatalogTab.java:
##########
@@ -364,34 +379,178 @@ private void renderDetail(Frame frame, Rect area,
List<CatalogEntry> sorted) {
area);
}
- private void openDocViewer() {
- if (ctx.openCatalogDocCallback == null || catalog == null) {
- return;
- }
+ private CatalogEntry selectedEntry() {
List<CatalogEntry> sorted = new ArrayList<>(filteredEntries);
sorted.sort(this::sortEntry);
Integer sel = tableState.selected();
- if (sel == null || sel < 0 || sel >= sorted.size()) {
+ return sel == null || sel < 0 || sel >= sorted.size() ? null :
sorted.get(sel);
+ }
+
+ private void openDocViewer() {
+ CatalogEntry entry = selectedEntry();
+ if (entry != null && entry.kamelet != null) {
+ openKameletDoc(entry);
+ return;
+ }
+ if (ctx.openCatalogDocCallback == null || catalog == null || entry ==
null) {
return;
}
- CatalogEntry entry = sorted.get(sel);
ctx.openCatalogDocCallback.accept(entry.name, entry.kind, catalog);
}
private void openOptionsViewer() {
- if (ctx.openOptionsCallback == null || catalog == null) {
+ CatalogEntry entry = selectedEntry();
+ if (entry != null && entry.kamelet != null) {
+ // the options of a Kamelet are its properties, which its doc lists
+ openKameletDoc(entry);
return;
}
- List<CatalogEntry> sorted = new ArrayList<>(filteredEntries);
- sorted.sort(this::sortEntry);
- Integer sel = tableState.selected();
- if (sel == null || sel < 0 || sel >= sorted.size()) {
+ if (ctx.openOptionsCallback == null || catalog == null || entry ==
null) {
return;
}
- CatalogEntry entry = sorted.get(sel);
ctx.openOptionsCallback.accept(entry.name, entry.kind, catalog);
}
+ private void openKameletDoc(CatalogEntry entry) {
+ if (ctx.openMarkdownCallback != null) {
+ ctx.openMarkdownCallback.accept(entry.name,
kameletMarkdown(entry.kamelet));
+ }
+ }
+
+ // ---- Kamelets (CAMEL-25416) ----
+
+ private static void kameletDetail(List<Line> lines, CatalogEntry entry,
int width) {
+ KameletDefinitions.Definition def = entry.kamelet;
+ lines.add(Line.from(Span.styled(" Kind: ", Style.EMPTY.dim()),
+ Span.styled("kamelet" + (def.type() != null ? " (" +
def.type() + ")" : ""), kindStyle("kamelet")),
+ entry.project ? Span.styled(" the project's own",
Theme.label().bold()) : Span.raw("")));
+ if (def.title() != null && !def.title().equals(def.name())) {
+ lines.add(Line.from(Span.styled(" Title: ", Style.EMPTY.dim()),
Span.raw(def.title())));
+ }
+ if (def.description() != null) {
+ lines.add(Line.from(Span.styled(" Description: ",
Style.EMPTY.dim()),
+ Span.raw(TuiHelper.truncate(def.description(),
Math.max(10, width - 18)))));
+ }
+ lines.add(Line.from(Span.styled(" From: ", Style.EMPTY.dim()),
Span.raw(def.source())));
+ String props = KameletDefinitions.propertyList(def);
+ lines.add(Line.from(Span.styled(" Properties: ", Style.EMPTY.dim()),
+ Span.raw(props.isEmpty() ? "none" : TuiHelper.truncate(props,
Math.max(10, width - 17)))));
+ lines.add(Line.from(Span.styled(" Use: ", Style.EMPTY.dim()),
+ Span.raw(("source".equals(def.type()) ? "from: " : "to: ") +
"kamelet:" + def.name()
Review Comment:
💡 **`"to: "` fallback when `def.type()` is null**
When `def.type()` returns null (no `spec.type` in the YAML), this defaults
to `"to: kamelet:…"` — which could be misleading for a kamelet that is actually
a source. Consider using a neutral label in the null case:
```suggestion
Span.raw(("source".equals(def.type()) ? "from: " :
def.type() != null ? "to: " : "uri: ") + "kamelet:" + def.name()
```
##########
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CatalogTab.java:
##########
@@ -364,34 +379,178 @@ private void renderDetail(Frame frame, Rect area,
List<CatalogEntry> sorted) {
area);
}
- private void openDocViewer() {
- if (ctx.openCatalogDocCallback == null || catalog == null) {
- return;
- }
+ private CatalogEntry selectedEntry() {
List<CatalogEntry> sorted = new ArrayList<>(filteredEntries);
sorted.sort(this::sortEntry);
Integer sel = tableState.selected();
- if (sel == null || sel < 0 || sel >= sorted.size()) {
+ return sel == null || sel < 0 || sel >= sorted.size() ? null :
sorted.get(sel);
+ }
+
+ private void openDocViewer() {
+ CatalogEntry entry = selectedEntry();
+ if (entry != null && entry.kamelet != null) {
+ openKameletDoc(entry);
+ return;
+ }
+ if (ctx.openCatalogDocCallback == null || catalog == null || entry ==
null) {
return;
}
- CatalogEntry entry = sorted.get(sel);
ctx.openCatalogDocCallback.accept(entry.name, entry.kind, catalog);
}
private void openOptionsViewer() {
- if (ctx.openOptionsCallback == null || catalog == null) {
+ CatalogEntry entry = selectedEntry();
+ if (entry != null && entry.kamelet != null) {
+ // the options of a Kamelet are its properties, which its doc lists
+ openKameletDoc(entry);
return;
}
- List<CatalogEntry> sorted = new ArrayList<>(filteredEntries);
- sorted.sort(this::sortEntry);
- Integer sel = tableState.selected();
- if (sel == null || sel < 0 || sel >= sorted.size()) {
+ if (ctx.openOptionsCallback == null || catalog == null || entry ==
null) {
return;
}
- CatalogEntry entry = sorted.get(sel);
ctx.openOptionsCallback.accept(entry.name, entry.kind, catalog);
}
+ private void openKameletDoc(CatalogEntry entry) {
+ if (ctx.openMarkdownCallback != null) {
+ ctx.openMarkdownCallback.accept(entry.name,
kameletMarkdown(entry.kamelet));
+ }
+ }
+
+ // ---- Kamelets (CAMEL-25416) ----
+
+ private static void kameletDetail(List<Line> lines, CatalogEntry entry,
int width) {
+ KameletDefinitions.Definition def = entry.kamelet;
+ lines.add(Line.from(Span.styled(" Kind: ", Style.EMPTY.dim()),
+ Span.styled("kamelet" + (def.type() != null ? " (" +
def.type() + ")" : ""), kindStyle("kamelet")),
+ entry.project ? Span.styled(" the project's own",
Theme.label().bold()) : Span.raw("")));
+ if (def.title() != null && !def.title().equals(def.name())) {
+ lines.add(Line.from(Span.styled(" Title: ", Style.EMPTY.dim()),
Span.raw(def.title())));
+ }
+ if (def.description() != null) {
+ lines.add(Line.from(Span.styled(" Description: ",
Style.EMPTY.dim()),
+ Span.raw(TuiHelper.truncate(def.description(),
Math.max(10, width - 18)))));
+ }
+ lines.add(Line.from(Span.styled(" From: ", Style.EMPTY.dim()),
Span.raw(def.source())));
+ String props = KameletDefinitions.propertyList(def);
+ lines.add(Line.from(Span.styled(" Properties: ", Style.EMPTY.dim()),
+ Span.raw(props.isEmpty() ? "none" : TuiHelper.truncate(props,
Math.max(10, width - 17)))));
+ lines.add(Line.from(Span.styled(" Use: ", Style.EMPTY.dim()),
+ Span.raw(("source".equals(def.type()) ? "from: " : "to: ") +
"kamelet:" + def.name()
+ + ", its properties under parameters:")));
+ }
+
+ /** The doc of a Kamelet: its type, where it comes from, how a route uses
it, and its properties. */
+ static String kameletMarkdown(KameletDefinitions.Definition def) {
+ StringBuilder md = new StringBuilder();
+ md.append("# ").append(def.title() != null ? def.title() :
def.name()).append("\n\n");
+ if (def.description() != null) {
+ md.append(def.description()).append("\n\n");
+ }
+ md.append("**Kamelet:** `").append(def.name()).append('`');
+ if (def.type() != null) {
+ md.append(" (").append(def.type()).append(')');
+ }
+ md.append(" \n**From:** ").append(def.source()).append("\n\n");
+ md.append("## Usage\n\n```yaml\n");
+ boolean source = "source".equals(def.type());
+ md.append(source ? "from:\n uri: kamelet:" : "- to:\n uri:
kamelet:").append(def.name()).append('\n');
+ List<KameletDefinitions.Property> required = new ArrayList<>();
+ for (KameletDefinitions.Property p : def.properties()) {
+ if (p.required() && p.defaultValue() == null) {
+ required.add(p);
+ }
+ }
+ if (!required.isEmpty()) {
+ md.append(source ? " parameters:\n" : " parameters:\n");
+ for (KameletDefinitions.Property p : required) {
+ md.append(source ? " " : "
").append(p.name()).append(": ...\n");
+ }
+ }
+ md.append("```\n\nThe properties go under `parameters:`: they are the
Kamelet's, not the options of the"
+ + " component it uses.\n\n## Properties\n\n");
+ if (def.properties().isEmpty()) {
+ md.append("None.\n");
+ } else {
+ md.append("| Name | Required | Type | Default | Description
|\n|---|---|---|---|---|\n");
+ for (KameletDefinitions.Property p : def.properties()) {
+ md.append("| ").append(p.name())
+ .append(" | ").append(p.required() && p.defaultValue()
== null ? "yes" : "")
+ .append(" | ").append(p.type() != null ? p.type() : "")
+ .append(" | ").append(p.defaultValue() != null ?
p.defaultValue() : "")
+ .append(" | ")
+ .append(p.description() != null ?
p.description().replace("|", "\\|").replace("\n", " ") : "")
+ .append(" |\n");
+ }
+ }
+ return md.toString();
+ }
+
+ private static final java.util.regex.Pattern KAMELET_URI
+ = java.util.regex.Pattern.compile("kamelet:([a-z0-9][a-z0-9-]*)");
+
+ /**
+ * The Kamelets of the catalog view: the project's own Kamelet files
always, the Kamelets of the catalog its routes
+ * use, and with the full catalog all the Kamelets of the catalog.
+ */
+ static List<CatalogEntry> kameletEntries(
+ Path dir, boolean full, java.util.Map<String,
KameletDefinitions.Definition> catalogKamelets) {
+ List<CatalogEntry> entries = new ArrayList<>();
+ java.util.Map<String, KameletDefinitions.Definition> project =
KameletDefinitions.projectKamelets(dir);
+ for (KameletDefinitions.Definition def : project.values()) {
+ entries.add(kameletEntry(def, true));
+ }
+ Set<String> used = full ? Set.of() : usedKamelets(dir);
+ for (KameletDefinitions.Definition def : catalogKamelets.values()) {
+ if (!project.containsKey(def.name()) && (full ||
used.contains(def.name()))) {
+ entries.add(kameletEntry(def, false));
+ }
+ }
+ return entries;
+ }
+
+ private static CatalogEntry kameletEntry(KameletDefinitions.Definition
def, boolean project) {
+ CatalogEntry entry = new CatalogEntry();
+ entry.name = def.name();
+ entry.kind = "kamelet";
+ entry.title = def.title() != null ? def.title() : def.name();
+ entry.description = def.description() != null ? def.description() : "";
+ entry.label = project ? "project" : def.type();
+ entry.kamelet = def;
+ entry.project = project;
+ return entry;
+ }
+
+ /** The Kamelets the route files of the directory send to or consume from,
kamelet:source and :sink left out. */
+ static Set<String> usedKamelets(Path dir) {
+ Set<String> used = new HashSet<>();
+ if (dir == null || !java.nio.file.Files.isDirectory(dir)) {
+ return used;
+ }
+ try (java.util.stream.Stream<Path> files =
java.nio.file.Files.walk(dir, 6)) {
Review Comment:
⚠️ **Depth mismatch with `KameletDefinitions.projectKamelets`**
`usedKamelets` walks with depth 6, but
`KameletDefinitions.projectKamelets(Path)` walks with depth 4. A project
kamelet definition at depth 5 or 6 (e.g.
`src/main/resources/kamelets/sub/dir/my.kamelet.yaml`) would be missed by
`projectKamelets` but its name could still appear in `usedKamelets` results —
it would then show up as a catalog kamelet rather than a project one. Either
align both depths (4 seems enough for typical project layouts) or document the
intentional mismatch.
##########
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CatalogTab.java:
##########
@@ -364,34 +379,178 @@ private void renderDetail(Frame frame, Rect area,
List<CatalogEntry> sorted) {
area);
}
- private void openDocViewer() {
- if (ctx.openCatalogDocCallback == null || catalog == null) {
- return;
- }
+ private CatalogEntry selectedEntry() {
List<CatalogEntry> sorted = new ArrayList<>(filteredEntries);
sorted.sort(this::sortEntry);
Integer sel = tableState.selected();
- if (sel == null || sel < 0 || sel >= sorted.size()) {
+ return sel == null || sel < 0 || sel >= sorted.size() ? null :
sorted.get(sel);
+ }
+
+ private void openDocViewer() {
+ CatalogEntry entry = selectedEntry();
+ if (entry != null && entry.kamelet != null) {
+ openKameletDoc(entry);
+ return;
+ }
+ if (ctx.openCatalogDocCallback == null || catalog == null || entry ==
null) {
return;
}
- CatalogEntry entry = sorted.get(sel);
ctx.openCatalogDocCallback.accept(entry.name, entry.kind, catalog);
}
private void openOptionsViewer() {
- if (ctx.openOptionsCallback == null || catalog == null) {
+ CatalogEntry entry = selectedEntry();
+ if (entry != null && entry.kamelet != null) {
+ // the options of a Kamelet are its properties, which its doc lists
+ openKameletDoc(entry);
return;
}
- List<CatalogEntry> sorted = new ArrayList<>(filteredEntries);
- sorted.sort(this::sortEntry);
- Integer sel = tableState.selected();
- if (sel == null || sel < 0 || sel >= sorted.size()) {
+ if (ctx.openOptionsCallback == null || catalog == null || entry ==
null) {
return;
}
- CatalogEntry entry = sorted.get(sel);
ctx.openOptionsCallback.accept(entry.name, entry.kind, catalog);
}
+ private void openKameletDoc(CatalogEntry entry) {
+ if (ctx.openMarkdownCallback != null) {
+ ctx.openMarkdownCallback.accept(entry.name,
kameletMarkdown(entry.kamelet));
+ }
+ }
+
+ // ---- Kamelets (CAMEL-25416) ----
+
+ private static void kameletDetail(List<Line> lines, CatalogEntry entry,
int width) {
+ KameletDefinitions.Definition def = entry.kamelet;
+ lines.add(Line.from(Span.styled(" Kind: ", Style.EMPTY.dim()),
+ Span.styled("kamelet" + (def.type() != null ? " (" +
def.type() + ")" : ""), kindStyle("kamelet")),
+ entry.project ? Span.styled(" the project's own",
Theme.label().bold()) : Span.raw("")));
+ if (def.title() != null && !def.title().equals(def.name())) {
+ lines.add(Line.from(Span.styled(" Title: ", Style.EMPTY.dim()),
Span.raw(def.title())));
+ }
+ if (def.description() != null) {
+ lines.add(Line.from(Span.styled(" Description: ",
Style.EMPTY.dim()),
+ Span.raw(TuiHelper.truncate(def.description(),
Math.max(10, width - 18)))));
+ }
+ lines.add(Line.from(Span.styled(" From: ", Style.EMPTY.dim()),
Span.raw(def.source())));
+ String props = KameletDefinitions.propertyList(def);
+ lines.add(Line.from(Span.styled(" Properties: ", Style.EMPTY.dim()),
+ Span.raw(props.isEmpty() ? "none" : TuiHelper.truncate(props,
Math.max(10, width - 17)))));
+ lines.add(Line.from(Span.styled(" Use: ", Style.EMPTY.dim()),
+ Span.raw(("source".equals(def.type()) ? "from: " : "to: ") +
"kamelet:" + def.name()
+ + ", its properties under parameters:")));
+ }
+
+ /** The doc of a Kamelet: its type, where it comes from, how a route uses
it, and its properties. */
+ static String kameletMarkdown(KameletDefinitions.Definition def) {
+ StringBuilder md = new StringBuilder();
+ md.append("# ").append(def.title() != null ? def.title() :
def.name()).append("\n\n");
+ if (def.description() != null) {
+ md.append(def.description()).append("\n\n");
+ }
+ md.append("**Kamelet:** `").append(def.name()).append('`');
+ if (def.type() != null) {
+ md.append(" (").append(def.type()).append(')');
+ }
+ md.append(" \n**From:** ").append(def.source()).append("\n\n");
+ md.append("## Usage\n\n```yaml\n");
+ boolean source = "source".equals(def.type());
+ md.append(source ? "from:\n uri: kamelet:" : "- to:\n uri:
kamelet:").append(def.name()).append('\n');
+ List<KameletDefinitions.Property> required = new ArrayList<>();
+ for (KameletDefinitions.Property p : def.properties()) {
+ if (p.required() && p.defaultValue() == null) {
+ required.add(p);
+ }
+ }
+ if (!required.isEmpty()) {
+ md.append(source ? " parameters:\n" : " parameters:\n");
+ for (KameletDefinitions.Property p : required) {
+ md.append(source ? " " : "
").append(p.name()).append(": ...\n");
+ }
+ }
+ md.append("```\n\nThe properties go under `parameters:`: they are the
Kamelet's, not the options of the"
+ + " component it uses.\n\n## Properties\n\n");
+ if (def.properties().isEmpty()) {
+ md.append("None.\n");
+ } else {
+ md.append("| Name | Required | Type | Default | Description
|\n|---|---|---|---|---|\n");
+ for (KameletDefinitions.Property p : def.properties()) {
+ md.append("| ").append(p.name())
+ .append(" | ").append(p.required() && p.defaultValue()
== null ? "yes" : "")
+ .append(" | ").append(p.type() != null ? p.type() : "")
+ .append(" | ").append(p.defaultValue() != null ?
p.defaultValue() : "")
+ .append(" | ")
+ .append(p.description() != null ?
p.description().replace("|", "\\|").replace("\n", " ") : "")
+ .append(" |\n");
+ }
+ }
+ return md.toString();
+ }
+
+ private static final java.util.regex.Pattern KAMELET_URI
+ = java.util.regex.Pattern.compile("kamelet:([a-z0-9][a-z0-9-]*)");
+
+ /**
+ * The Kamelets of the catalog view: the project's own Kamelet files
always, the Kamelets of the catalog its routes
+ * use, and with the full catalog all the Kamelets of the catalog.
+ */
+ static List<CatalogEntry> kameletEntries(
+ Path dir, boolean full, java.util.Map<String,
KameletDefinitions.Definition> catalogKamelets) {
+ List<CatalogEntry> entries = new ArrayList<>();
+ java.util.Map<String, KameletDefinitions.Definition> project =
KameletDefinitions.projectKamelets(dir);
+ for (KameletDefinitions.Definition def : project.values()) {
+ entries.add(kameletEntry(def, true));
+ }
+ Set<String> used = full ? Set.of() : usedKamelets(dir);
+ for (KameletDefinitions.Definition def : catalogKamelets.values()) {
+ if (!project.containsKey(def.name()) && (full ||
used.contains(def.name()))) {
+ entries.add(kameletEntry(def, false));
+ }
+ }
+ return entries;
+ }
+
+ private static CatalogEntry kameletEntry(KameletDefinitions.Definition
def, boolean project) {
+ CatalogEntry entry = new CatalogEntry();
+ entry.name = def.name();
+ entry.kind = "kamelet";
+ entry.title = def.title() != null ? def.title() : def.name();
+ entry.description = def.description() != null ? def.description() : "";
+ entry.label = project ? "project" : def.type();
Review Comment:
💡 **`entry.label` can be null when `project=false` and `def.type()` is null**
`def.type()` can return null for kamelets without an explicit `spec.type`.
While `renderTable` already guards against null, `sortEntry` calls
`a.label.compareToIgnoreCase(b.label)` via the default sort path and would NPE
on a null label. Guard at the source:
```suggestion
entry.label = project ? "project" : (def.type() != null ? def.type()
: "");
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]