This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new d9b2a8bb3992 CAMEL-24804: camel-jbang - camel_catalog_doc for simple:
an optionsFilter that names a function group returns that group only (#26566)
d9b2a8bb3992 is described below
commit d9b2a8bb3992ebf941a16a4c7f41ecb477ee2ff7
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Sep 17 22:37:38 2026 +0200
CAMEL-24804: camel-jbang - camel_catalog_doc for simple: an optionsFilter
that names a function group returns that group only (#26566)
* CAMEL-24804: camel-jbang - camel_catalog_doc for simple: an optionsFilter
that names a function group returns that group only, an operator kind that kind
only, and a function or operator name comes first
The filter was one substring match over name, display name, description and
group, so a model that followed
the hint and asked for the date group got every function whose description
mentions a date, and string most
of the catalog. A small model then reads a page of functions instead of a
few with examples.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
* CAMEL-24804: a function name comes first even when a group has the same
name; bodyOneLine belongs to the core group, not a group of its own
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---------
Signed-off-by: Claus Ibsen <[email protected]>
Co-authored-by: Claude Fable 5.1 <[email protected]>
---
.../org/apache/camel/catalog/languages/simple.json | 2 +-
.../org/apache/camel/language/simple/simple.json | 2 +-
.../camel/language/simple/SimpleConstants.java | 2 +-
.../modules/ROOT/pages/camel-jbang-mcp.adoc | 4 +-
.../dsl/jbang/core/commands/ai/CatalogDocs.java | 53 +++++++++++++++++++---
.../jbang/core/commands/ai/CatalogDocsTest.java | 38 ++++++++++++++++
6 files changed, 91 insertions(+), 10 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json
index b807033f3f3d..4e7580acfd4e 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json
@@ -65,7 +65,7 @@
"bean(name.method)": { "index": 37, "kind": "function", "displayName":
"Call Java Bean", "group": "core", "label": "core", "required": false,
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Calls a Java bean. The
name of the bean can also refer to a class name using type prefix as follows
`bean:type:com.foo.MyClass`. If no method name is given then Camel will
automatic attempt to find the best meth [...]
"body": { "index": 38, "kind": "function", "displayName": "Body", "group":
"core", "label": "core", "required": false, "javaType": "Object", "prefix":
"${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The message body", "ognl": true, "suffix": "}",
"examples": [ "${body} -> Hello World \/\/ when body is the string 'Hello
World'" ] },
"bodyAs(type)": { "index": 39, "kind": "function", "displayName": "Body
As", "group": "core", "label": "core", "required": false, "javaType": "Object",
"prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "Converts the message body to the given type
(classname).", "ognl": true, "suffix": "}", "params": [ { "name": "type",
"javaType": "String", "required": true, "description": "The target type
classname (e.g. String, int, byte [...]
- "bodyOneLine": { "index": 40, "kind": "function", "displayName": "Body One
Line", "group": "function", "label": "function", "required": false, "javaType":
"String", "prefix": "${", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Converts the body to a
String and removes all line-breaks, so the string is in one line.", "ognl":
false, "suffix": "}", "examples": [ "${bodyOneLine} -> Hello World \/\/ when
body is 'Hello\\nWorld'" ] },
+ "bodyOneLine": { "index": 40, "kind": "function", "displayName": "Body One
Line", "group": "core", "label": "core", "required": false, "javaType":
"String", "prefix": "${", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Converts the body to a
String and removes all line-breaks, so the string is in one line.", "ognl":
false, "suffix": "}", "examples": [ "${bodyOneLine} -> Hello World \/\/ when
body is 'Hello\\nWorld'" ] },
"bodyType": { "index": 41, "kind": "function", "displayName": "Body Type",
"group": "core", "label": "core", "required": false, "javaType": "Class",
"prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The message body class type", "ognl": false,
"suffix": "}", "examples": [ "${bodyType} -> java.lang.String \/\/ when body is
a String" ] },
"camelContext": { "index": 42, "kind": "function", "displayName": "Camel
Context", "group": "core", "label": "core", "required": false, "javaType":
"Object", "prefix": "${", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The Camel Context",
"ognl": true, "suffix": "}" },
"camelId": { "index": 43, "kind": "function", "displayName": "Camel Id",
"group": "core", "label": "core", "required": false, "javaType": "String",
"prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The name of the CamelContext", "ognl": false,
"suffix": "}", "examples": [ "${camelId} -> camel-1" ] },
diff --git
a/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json
b/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json
index b807033f3f3d..4e7580acfd4e 100644
---
a/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json
+++
b/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json
@@ -65,7 +65,7 @@
"bean(name.method)": { "index": 37, "kind": "function", "displayName":
"Call Java Bean", "group": "core", "label": "core", "required": false,
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "Calls a Java bean. The
name of the bean can also refer to a class name using type prefix as follows
`bean:type:com.foo.MyClass`. If no method name is given then Camel will
automatic attempt to find the best meth [...]
"body": { "index": 38, "kind": "function", "displayName": "Body", "group":
"core", "label": "core", "required": false, "javaType": "Object", "prefix":
"${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The message body", "ognl": true, "suffix": "}",
"examples": [ "${body} -> Hello World \/\/ when body is the string 'Hello
World'" ] },
"bodyAs(type)": { "index": 39, "kind": "function", "displayName": "Body
As", "group": "core", "label": "core", "required": false, "javaType": "Object",
"prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "Converts the message body to the given type
(classname).", "ognl": true, "suffix": "}", "params": [ { "name": "type",
"javaType": "String", "required": true, "description": "The target type
classname (e.g. String, int, byte [...]
- "bodyOneLine": { "index": 40, "kind": "function", "displayName": "Body One
Line", "group": "function", "label": "function", "required": false, "javaType":
"String", "prefix": "${", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Converts the body to a
String and removes all line-breaks, so the string is in one line.", "ognl":
false, "suffix": "}", "examples": [ "${bodyOneLine} -> Hello World \/\/ when
body is 'Hello\\nWorld'" ] },
+ "bodyOneLine": { "index": 40, "kind": "function", "displayName": "Body One
Line", "group": "core", "label": "core", "required": false, "javaType":
"String", "prefix": "${", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Converts the body to a
String and removes all line-breaks, so the string is in one line.", "ognl":
false, "suffix": "}", "examples": [ "${bodyOneLine} -> Hello World \/\/ when
body is 'Hello\\nWorld'" ] },
"bodyType": { "index": 41, "kind": "function", "displayName": "Body Type",
"group": "core", "label": "core", "required": false, "javaType": "Class",
"prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The message body class type", "ognl": false,
"suffix": "}", "examples": [ "${bodyType} -> java.lang.String \/\/ when body is
a String" ] },
"camelContext": { "index": 42, "kind": "function", "displayName": "Camel
Context", "group": "core", "label": "core", "required": false, "javaType":
"Object", "prefix": "${", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The Camel Context",
"ognl": true, "suffix": "}" },
"camelId": { "index": 43, "kind": "function", "displayName": "Camel Id",
"group": "core", "label": "core", "required": false, "javaType": "String",
"prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false,
"secret": false, "description": "The name of the CamelContext", "ognl": false,
"suffix": "}", "examples": [ "${camelId} -> camel-1" ] },
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java
index e733d619a3cc..e5b61924a59a 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java
@@ -230,7 +230,7 @@ public final class SimpleConstants {
public static final String BODY_AS = "bodyAs(type)";
@Metadata(description = "Converts the body to a String and removes all
line-breaks, so the string is in one line.",
- javaType = "String", label = "function",
+ javaType = "String", label = "core",
examples = { "${bodyOneLine} -> Hello World // when body is
'Hello\\nWorld'" })
public static final String BODY_ONE_LINE = "bodyOneLine";
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
index bb95dd07da4a..23a95e7f3ad6 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
@@ -269,7 +269,9 @@ project `directory` as an argument, the runtime tools take
the integration `name
Maven coordinates, and for a component the rules of its endpoint URI spelled
out (which
options are path parts, the YAML `uri` plus `parameters` form, placeholders,
`RAW()`). For the `simple`
language the syntax rules, functions and operators (their count and names by
group, or with `optionsFilter`
- the matching ones with parameters and examples), and `docPage` serves the
functions, operators, OGNL and
+ the matching ones with parameters and examples: a group name such as `date`
gives that group only, an
+ operator kind such as `logical` that kind only, a function or operator name
comes first, and any other word
+ matches the names and descriptions), and `docPage` serves the functions,
operators, OGNL and
advanced pages. With `endpoint` it checks a URI against the catalog: unknown
options with the closest real
names, invalid values, missing path parts, consumer options on a producer
endpoint. `includeHeaders=true`
adds the message headers of a component (the `CamelXxx` names, their
constants, types and consumer or
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
index 9ef75c5527be..717e5bb60732 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
@@ -1084,15 +1084,30 @@ public final class CatalogDocs {
if (functions != null && !functions.isEmpty()) {
result.put("functionCount", functions.size());
if (filter != null) {
+ // a function name comes first, even when a group has the same
name; a group name is then that group
+ // and nothing else (date is two functions, not every function
whose description mentions a date);
+ // the rest is a word match
+ boolean group = functions.stream().anyMatch(fn ->
filter.equalsIgnoreCase(fn.getGroup()));
+ JsonArray exact = new JsonArray();
JsonArray arr = new JsonArray();
for (LanguageModel.LanguageFunctionModel fn : functions) {
- if (matchesOptionFilter(fn, filter)
+ if (isFunctionName(fn, filter)) {
+ exact.add(functionToJson(fn));
+ } else if (group) {
+ if (filter.equalsIgnoreCase(fn.getGroup())) {
+ arr.add(functionToJson(fn));
+ }
+ } else if (matchesOptionFilter(fn, filter)
|| (fn.getDisplayName() != null &&
fn.getDisplayName().toLowerCase().contains(filter))) {
arr.add(functionToJson(fn));
}
}
- result.put("functions", arr);
- result.put("matchedFunctions", arr.size());
+ exact.addAll(arr);
+ result.put("functions", exact);
+ result.put("matchedFunctions", exact.size());
+ if (group) {
+ result.put("functionGroup", filter);
+ }
} else {
Map<String, JsonArray> groups = new TreeMap<>();
for (LanguageModel.LanguageFunctionModel fn : functions) {
@@ -1109,15 +1124,28 @@ public final class CatalogDocs {
if (operators != null && !operators.isEmpty()) {
result.put("operatorCount", operators.size());
if (filter != null) {
+ // an operator kind (binary, logical, unary...) is that kind
only; an operator itself comes first
+ boolean kind = operators.stream().anyMatch(op ->
filter.equalsIgnoreCase(op.getOperatorKind()));
+ JsonArray exact = new JsonArray();
JsonArray arr = new JsonArray();
for (LanguageModel.LanguageOperatorModel op : operators) {
- if (matchesOptionFilter(op, filter)
+ if (kind) {
+ if (filter.equalsIgnoreCase(op.getOperatorKind())) {
+ arr.add(operatorToJson(op));
+ }
+ } else if (filter.equalsIgnoreCase(op.getName())) {
+ exact.add(operatorToJson(op));
+ } else if (matchesOptionFilter(op, filter)
|| (op.getOperatorKind() != null &&
op.getOperatorKind().toLowerCase().contains(filter))) {
arr.add(operatorToJson(op));
}
}
- result.put("operators", arr);
- result.put("matchedOperators", arr.size());
+ exact.addAll(arr);
+ result.put("operators", exact);
+ result.put("matchedOperators", exact.size());
+ if (kind) {
+ result.put("operatorKind", filter);
+ }
} else {
JsonArray syntaxes = new JsonArray();
for (LanguageModel.LanguageOperatorModel op : operators) {
@@ -1128,6 +1156,19 @@ public final class CatalogDocs {
}
}
+ /**
+ * Whether the filter is the function's name: {@code random} or {@code
random(min,max)} for {@code random(min,max)}.
+ */
+ private static boolean isFunctionName(LanguageModel.LanguageFunctionModel
fn, String filter) {
+ String name = fn.getName();
+ if (name == null) {
+ return false;
+ }
+ int paren = name.indexOf('(');
+ String bare = paren > 0 ? name.substring(0, paren) : name;
+ return filter.equalsIgnoreCase(name) || filter.equalsIgnoreCase(bare);
+ }
+
private static JsonObject
functionToJson(LanguageModel.LanguageFunctionModel fn) {
JsonObject o = new JsonObject();
o.put("name", fn.getName());
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java
index d8fc384d87b1..97cbf8277fbe 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java
@@ -179,6 +179,44 @@ class CatalogDocsTest {
assertEquals("LHS == RHS", eq.getString("syntax"));
}
+ @Test
+ void aGroupNameReturnsTheGroupOnlyAndAFunctionNameComesFirst() throws
Exception {
+ // CAMEL-24804: date is a group of two functions, and two more mention
a date in their description
+ JsonObject dates = catalogDoc(Map.of("name", "simple", "kind",
"language", "optionsFilter", "date"));
+ assertEquals("date", dates.getString("functionGroup"));
+ assertEquals(2, dates.getInteger("matchedFunctions"));
+ dates.getCollection("functions").forEach(fn -> assertEquals("date",
((JsonObject) fn).getString("group")));
+
+ // string is a group of a dozen or so, while most descriptions say
"string" somewhere
+ JsonObject strings = catalogDoc(Map.of("name", "simple", "kind",
"language", "optionsFilter", "string"));
+ assertEquals("string", strings.getString("functionGroup"));
+ assertTrue(strings.getInteger("matchedFunctions") < 25,
+ "the group, not the catalog: " +
strings.getInteger("matchedFunctions"));
+ strings.getCollection("functions").forEach(fn ->
assertEquals("string", ((JsonObject) fn).getString("group")));
+
+ // a function name comes first, with or without its parameters, then
the word matches
+ for (String name : List.of("random", "RANDOM", "random(min,max)")) {
+ JsonObject random = catalogDoc(Map.of("name", "simple", "kind",
"language", "optionsFilter", name));
+ assertNull(random.get("functionGroup"));
+ JsonObject first = (JsonObject)
random.getCollection("functions").iterator().next();
+ assertEquals("random(min,max)", first.getString("name"), name);
+ }
+
+ // a function name comes first even when a group has the same name
+ JsonObject function = catalogDoc(Map.of("name", "simple", "kind",
"language", "optionsFilter", "function"));
+ assertEquals("function(name,exp)",
+ ((JsonObject)
function.getCollection("functions").iterator().next()).getString("name"));
+
+ // an operator kind is that kind only, and an operator itself comes
first
+ JsonObject logical = catalogDoc(Map.of("name", "simple", "kind",
"language", "optionsFilter", "logical"));
+ assertEquals("logical", logical.getString("operatorKind"));
+ assertTrue(logical.getInteger("matchedOperators") >= 2);
+ logical.getCollection("operators").forEach(op ->
assertEquals("logical", ((JsonObject) op).getString("kind")));
+ JsonObject eq = catalogDoc(Map.of("name", "simple", "kind",
"language", "optionsFilter", "=="));
+ assertNull(eq.get("operatorKind"));
+ assertEquals("==", ((JsonObject)
eq.getCollection("operators").iterator().next()).getString("name"));
+ }
+
@Test
void anInterfaceNameFindsTheBuiltInBeans() {
org.apache.camel.catalog.CamelCatalog catalog = new
org.apache.camel.catalog.DefaultCamelCatalog();