gnodet commented on code in PR #23600:
URL: https://github.com/apache/camel/pull/23600#discussion_r3320252721
##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Ask.java:
##########
@@ -720,6 +749,213 @@ private String executeGetExampleFile(JsonObject args) {
}
}
+ // ---- CLI tools ----
+
+ @SuppressWarnings("unchecked")
+ private List<JsonObject> loadCommandMetadata() {
+ try (InputStream is = getClass().getClassLoader()
+
.getResourceAsStream("META-INF/camel-jbang-commands-metadata.json")) {
+ if (is == null) {
+ return List.of();
+ }
+ String json = IOHelper.loadText(is);
+ JsonObject root = (JsonObject) Jsoner.deserialize(json);
Review Comment:
Fixed — metadata is now cached in a `commandMetadataCache` field after first
load.
_Claude Code on behalf of Guillaume Nodet_
##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Ask.java:
##########
@@ -720,6 +749,213 @@ private String executeGetExampleFile(JsonObject args) {
}
}
+ // ---- CLI tools ----
+
+ @SuppressWarnings("unchecked")
+ private List<JsonObject> loadCommandMetadata() {
+ try (InputStream is = getClass().getClassLoader()
+
.getResourceAsStream("META-INF/camel-jbang-commands-metadata.json")) {
+ if (is == null) {
+ return List.of();
+ }
+ String json = IOHelper.loadText(is);
+ JsonObject root = (JsonObject) Jsoner.deserialize(json);
+ Object commands = root.get("commands");
+ if (commands instanceof Collection<?>) {
+ return ((Collection<Object>) commands).stream()
+ .filter(JsonObject.class::isInstance)
+ .map(JsonObject.class::cast)
+ .toList();
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ return List.of();
+ }
+
+ @SuppressWarnings("unchecked")
+ private void collectCommands(List<JsonObject> commands, List<JsonObject>
result, String filter) {
+ for (JsonObject cmd : commands) {
+ String fullName = cmd.getString("fullName");
+ String description = cmd.getString("description");
+ boolean matches = filter == null || filter.isBlank()
+ || (fullName != null &&
fullName.toLowerCase().contains(filter.toLowerCase()))
+ || (description != null &&
description.toLowerCase().contains(filter.toLowerCase()));
+ if (matches) {
+ JsonObject entry = new JsonObject();
+ entry.put("command", fullName);
+ entry.put("description", description);
+ Object subs = cmd.get("subcommands");
+ if (subs instanceof Collection<?> subList &&
!subList.isEmpty()) {
+ entry.put("hasSubcommands", true);
+ entry.put("subcommandCount", subList.size());
+ }
+ result.add(entry);
+ }
+ Object subs = cmd.get("subcommands");
+ if (subs instanceof Collection<?>) {
+ collectCommands(
+ ((Collection<Object>) subs).stream()
+ .filter(JsonObject.class::isInstance)
+ .map(JsonObject.class::cast)
+ .toList(),
+ result, filter);
+ }
+ }
+ }
+
+ private String executeCliListCommands(JsonObject args) {
+ String filter = args.getString("filter");
+ List<JsonObject> commands = loadCommandMetadata();
+ List<JsonObject> result = new ArrayList<>();
+ collectCommands(commands, result, filter);
+
+ JsonObject response = new JsonObject();
+ response.put("count", result.size());
+ response.put("commands", result);
+ return response.toJson();
+ }
+
+ @SuppressWarnings("unchecked")
+ private JsonObject findCommand(List<JsonObject> commands, String fullName)
{
+ for (JsonObject cmd : commands) {
+ if (fullName.equals(cmd.getString("fullName"))) {
+ return cmd;
+ }
+ Object subs = cmd.get("subcommands");
+ if (subs instanceof Collection<?>) {
+ JsonObject found = findCommand(
+ ((Collection<Object>) subs).stream()
+ .filter(JsonObject.class::isInstance)
+ .map(JsonObject.class::cast)
+ .toList(),
+ fullName);
+ if (found != null) {
+ return found;
+ }
+ }
+ }
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ private String executeCliCommandHelp(JsonObject args) {
+ String command = args.getString("command");
+ if (command == null || command.isBlank()) {
+ return "Error: command name is required";
+ }
+
+ List<JsonObject> commands = loadCommandMetadata();
+ JsonObject cmd = findCommand(commands, command);
+ if (cmd == null) {
+ return "Command not found: " + command + ". Use cli_list_commands
to see available commands.";
+ }
+
+ JsonObject response = new JsonObject();
+ response.put("command", cmd.getString("fullName"));
+ response.put("description", cmd.getString("description"));
+
+ Object options = cmd.get("options");
+ if (options instanceof Collection<?>) {
+ List<JsonObject> opts = ((Collection<Object>) options).stream()
+ .filter(JsonObject.class::isInstance)
+ .map(JsonObject.class::cast)
+ .map(opt -> {
+ JsonObject o = new JsonObject();
+ o.put("names", opt.getString("names"));
+ o.put("description", opt.getString("description"));
+ o.put("type", opt.getString("type"));
+ String dv = opt.getString("defaultValue");
+ if (dv != null) {
+ o.put("defaultValue", dv);
+ }
+ return o;
+ })
+ .toList();
+ response.put("options", opts);
+ }
+
+ Object subs = cmd.get("subcommands");
+ if (subs instanceof Collection<?> subList && !subList.isEmpty()) {
+ List<JsonObject> subSummaries = ((Collection<Object>)
subList).stream()
+ .filter(JsonObject.class::isInstance)
+ .map(JsonObject.class::cast)
+ .map(sub -> {
+ JsonObject s = new JsonObject();
+ s.put("command", sub.getString("fullName"));
+ s.put("description", sub.getString("description"));
+ return s;
+ })
+ .toList();
+ response.put("subcommands", subSummaries);
+ }
+
+ return response.toJson();
+ }
+
+ private String executeCliExec(JsonObject args) {
+ String command = args.getString("command");
+ if (command == null || command.isBlank()) {
+ return "Error: command is required";
+ }
+
+ picocli.CommandLine commandLine = CamelJBangMain.getCommandLine();
+ if (commandLine == null) {
+ return "Error: CLI not available";
+ }
+
+ String[] cmdArgs = command.trim().split("\\s+");
+
+ // capture output by temporarily swapping the Printer on main
+ StringBuilder captured = new StringBuilder();
+ Printer capturingPrinter = new Printer() {
Review Comment:
Fixed — replaced `split("\\s+")` with a proper `tokenizeCommand()` that
handles single and double quoted strings. Added unit tests covering this.
_Claude Code on behalf of Guillaume Nodet_
##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Ask.java:
##########
@@ -720,6 +749,213 @@ private String executeGetExampleFile(JsonObject args) {
}
}
+ // ---- CLI tools ----
+
+ @SuppressWarnings("unchecked")
+ private List<JsonObject> loadCommandMetadata() {
+ try (InputStream is = getClass().getClassLoader()
+
.getResourceAsStream("META-INF/camel-jbang-commands-metadata.json")) {
+ if (is == null) {
+ return List.of();
+ }
+ String json = IOHelper.loadText(is);
+ JsonObject root = (JsonObject) Jsoner.deserialize(json);
+ Object commands = root.get("commands");
+ if (commands instanceof Collection<?>) {
+ return ((Collection<Object>) commands).stream()
+ .filter(JsonObject.class::isInstance)
+ .map(JsonObject.class::cast)
+ .toList();
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ return List.of();
+ }
+
+ @SuppressWarnings("unchecked")
+ private void collectCommands(List<JsonObject> commands, List<JsonObject>
result, String filter) {
+ for (JsonObject cmd : commands) {
+ String fullName = cmd.getString("fullName");
+ String description = cmd.getString("description");
+ boolean matches = filter == null || filter.isBlank()
+ || (fullName != null &&
fullName.toLowerCase().contains(filter.toLowerCase()))
+ || (description != null &&
description.toLowerCase().contains(filter.toLowerCase()));
+ if (matches) {
+ JsonObject entry = new JsonObject();
+ entry.put("command", fullName);
+ entry.put("description", description);
+ Object subs = cmd.get("subcommands");
+ if (subs instanceof Collection<?> subList &&
!subList.isEmpty()) {
+ entry.put("hasSubcommands", true);
+ entry.put("subcommandCount", subList.size());
+ }
+ result.add(entry);
+ }
+ Object subs = cmd.get("subcommands");
+ if (subs instanceof Collection<?>) {
+ collectCommands(
+ ((Collection<Object>) subs).stream()
+ .filter(JsonObject.class::isInstance)
+ .map(JsonObject.class::cast)
+ .toList(),
+ result, filter);
+ }
+ }
+ }
+
+ private String executeCliListCommands(JsonObject args) {
+ String filter = args.getString("filter");
+ List<JsonObject> commands = loadCommandMetadata();
+ List<JsonObject> result = new ArrayList<>();
+ collectCommands(commands, result, filter);
+
+ JsonObject response = new JsonObject();
+ response.put("count", result.size());
+ response.put("commands", result);
+ return response.toJson();
+ }
+
+ @SuppressWarnings("unchecked")
+ private JsonObject findCommand(List<JsonObject> commands, String fullName)
{
+ for (JsonObject cmd : commands) {
+ if (fullName.equals(cmd.getString("fullName"))) {
+ return cmd;
+ }
+ Object subs = cmd.get("subcommands");
+ if (subs instanceof Collection<?>) {
+ JsonObject found = findCommand(
+ ((Collection<Object>) subs).stream()
+ .filter(JsonObject.class::isInstance)
+ .map(JsonObject.class::cast)
+ .toList(),
+ fullName);
+ if (found != null) {
+ return found;
+ }
+ }
+ }
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ private String executeCliCommandHelp(JsonObject args) {
+ String command = args.getString("command");
+ if (command == null || command.isBlank()) {
+ return "Error: command name is required";
+ }
+
+ List<JsonObject> commands = loadCommandMetadata();
+ JsonObject cmd = findCommand(commands, command);
+ if (cmd == null) {
+ return "Command not found: " + command + ". Use cli_list_commands
to see available commands.";
+ }
+
+ JsonObject response = new JsonObject();
+ response.put("command", cmd.getString("fullName"));
+ response.put("description", cmd.getString("description"));
+
+ Object options = cmd.get("options");
+ if (options instanceof Collection<?>) {
+ List<JsonObject> opts = ((Collection<Object>) options).stream()
+ .filter(JsonObject.class::isInstance)
+ .map(JsonObject.class::cast)
+ .map(opt -> {
+ JsonObject o = new JsonObject();
+ o.put("names", opt.getString("names"));
+ o.put("description", opt.getString("description"));
+ o.put("type", opt.getString("type"));
+ String dv = opt.getString("defaultValue");
+ if (dv != null) {
+ o.put("defaultValue", dv);
+ }
+ return o;
+ })
+ .toList();
+ response.put("options", opts);
+ }
+
+ Object subs = cmd.get("subcommands");
+ if (subs instanceof Collection<?> subList && !subList.isEmpty()) {
+ List<JsonObject> subSummaries = ((Collection<Object>)
subList).stream()
+ .filter(JsonObject.class::isInstance)
+ .map(JsonObject.class::cast)
+ .map(sub -> {
+ JsonObject s = new JsonObject();
+ s.put("command", sub.getString("fullName"));
+ s.put("description", sub.getString("description"));
+ return s;
+ })
+ .toList();
+ response.put("subcommands", subSummaries);
+ }
+
+ return response.toJson();
+ }
+
+ private String executeCliExec(JsonObject args) {
+ String command = args.getString("command");
+ if (command == null || command.isBlank()) {
+ return "Error: command is required";
+ }
+
+ picocli.CommandLine commandLine = CamelJBangMain.getCommandLine();
+ if (commandLine == null) {
+ return "Error: CLI not available";
+ }
+
+ String[] cmdArgs = command.trim().split("\\s+");
+
+ // capture output by temporarily swapping the Printer on main
+ StringBuilder captured = new StringBuilder();
+ Printer capturingPrinter = new Printer() {
+ @Override
+ public void println() {
+ captured.append(System.lineSeparator());
+ }
+
+ @Override
+ public void println(String line) {
+ captured.append(line).append(System.lineSeparator());
+ }
+
+ @Override
+ public void print(String output) {
+ captured.append(output);
+ }
+
+ @Override
+ public void printf(String format, Object... fmtArgs) {
+ captured.append(String.format(format, fmtArgs));
+ }
+ };
+
+ // also capture PicoCLI's own output (usage/help text)
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ commandLine.setOut(pw);
+ commandLine.setErr(pw);
+
+ Printer originalPrinter = getMain().getOut();
+ getMain().setOut(capturingPrinter);
+ try {
+ int exitCode = commandLine.execute(cmdArgs);
+ pw.flush();
+ String output = captured.toString() + sw.toString();
+ if (output.isBlank() && exitCode != 0) {
+ return "Command exited with code " + exitCode;
+ }
+ if (output.length() > 32768) {
+ output = output.substring(0, 32768) + "\n... (truncated)";
+ }
+ return output;
+ } catch (Exception e) {
+ return "Error executing command: " + e.getMessage();
Review Comment:
Fixed — `commandLine.setOut(originalOut)` and
`commandLine.setErr(originalErr)` are now restored in the `finally` block
alongside the Printer.
_Claude Code on behalf of Guillaume Nodet_
--
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]