This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch fix/CAMEL-24874
in repository https://gitbox.apache.org/repos/asf/camel.git

commit d18ec2cd50a143bd4181b83af5c0786c90790dc8
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Sep 21 13:20:49 2026 +0200

    CAMEL-24874: camel-core-languages - the simple init block operators := and 
~:= are in the catalog
    
    The init block of a simple expression ($init{ ... }init$) assigns local
    variables with := and declares local custom functions with ~:= (usually a
    ~> chain), but the catalog had no operator entry for either, so nothing
    that reads simple.json knew they existed: camel_catalog_doc / 
tui_catalog_doc
    (operator syntax list and optionsFilter), the generated JS validator, the
    MCP server and any agent fetching the catalog. The docs were the only
    source, and docPage=advanced is the whole simple-advanced page.
    
    Both operators are declared in SimpleOperatorConstants (kind init) with
    their syntax and complete examples, listed in the operators doc table with
    a link to the init block sections, and the unfinished sentence and typos
    of the "Init Blocks with custom functions" section are fixed.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../apache/camel/catalog/docs/simple-advanced.adoc   |  4 +---
 .../apache/camel/catalog/docs/simple-operators.adoc  |  2 ++
 .../org/apache/camel/catalog/languages/simple.json   |  4 +++-
 .../camel/catalog/simple/camel-simple-validator.js   |  4 +++-
 .../org/apache/camel/language/simple/simple.json     |  4 +++-
 .../modules/languages/pages/simple-advanced.adoc     |  4 +---
 .../modules/languages/pages/simple-operators.adoc    |  2 ++
 .../language/simple/SimpleOperatorConstants.java     | 20 ++++++++++++++++++++
 .../dsl/jbang/core/commands/ai/CatalogDocsTest.java  | 15 +++++++++++++++
 9 files changed, 50 insertions(+), 9 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc
index 58c5c2057aed..8925f33c254f 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc
@@ -160,9 +160,7 @@ and then refer to the file such as 
`resource:classpath:mymapping.txt` where the
 
 === Init Blocks with custom functions
 
-You can also declare custom functions using
-
-Inside the init block, it is a lso possible to define custom functions in the 
syntax `$nane ~:= <statement>;` where you can then use simple language to 
declare
+Inside the init block, it is also possible to define custom functions in the 
syntax `$name ~:= <statement>;` where you can then use simple language to 
declare
 the structure of the function. Then you can later use these custom functions 
in your simple language expressions.
 
 For example to create a function that can cleanup a `String` value:
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-operators.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-operators.adoc
index da22008cd82e..e5354e79f7db 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-operators.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-operators.adoc
@@ -115,6 +115,8 @@ The following other operators can be used:
 |`?:` | The elvis operator returns the left-hand side if it has an effective 
Boolean value of true, otherwise it returns the right-hand side. This is useful 
for providing fallback values when an expression may evaluate to a value with 
an effective Boolean value of false (such as `null`, `false`, `0`, empty/blank 
string).
 |`~>` | The chain operator is used in the situations where multiple nested 
functions need to be applied to a value, while making it easy to read. The 
value on the left-hand-side is evaluated, and set as the new message body 
before evaluating the right-hand-side function. This concept is similar to the 
xref:eips:pipeline-eip.adoc[Pipeline EIP].
 |`?~>` | The null-safe chain operator, where the chain will not continue when 
a function returned `null`.
+|`:=` | Assigns a local variable in the init block at the top of an expression 
(`$init{ $minAge := 18; }init$`), see 
xref:simple-advanced.adoc#_init_blocks[Init Blocks].
+|`~:=` | Declares a local custom function in the init block, usually as a 
chain (`$init{ $clean ~:= ${trim()} ~> ${uppercase()}; }init$`), called as 
`${clean()}`, see 
xref:simple-advanced.adoc#_init_blocks_with_custom_functions[Init Blocks with 
custom functions].
 |====
 
 ==== Ternary Operator
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 4e7580acfd4e..e2b8a01cf9be 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
@@ -213,6 +213,8 @@
     "? :": { "index": 30, "kind": "operator", "displayName": "Ternary", 
"label": "ternary", "required": false, "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "Ternary conditional 
operator. Evaluates the predicate and returns trueValue if true, falseValue if 
false. Requires spaces around both ? and : tokens.", "operatorKind": "ternary", 
"operatorSyntax": "predicate ? trueValue : falseValue", "precedence": 25, 
"examples": [ "${header.foo} >  [...]
     "~>": { "index": 31, "kind": "operator", "displayName": "Chain", "label": 
"chain", "required": false, "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Pipes the result of the 
left expression as input body to the right expression. Use $param in the right 
expression to reference the piped value explicitly.", "operatorKind": "chain", 
"operatorSyntax": "expr ~> expr", "precedence": 5, "examples": [ "${trim()} ~> 
${uppercase()}", "${substrin [...]
     "?~>": { "index": 32, "kind": "operator", "displayName": "Chain null 
safe", "label": "chain", "required": false, "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Null-safe chain operator. Same as ~> but stops chaining and returns null if 
the left expression evaluates to null.", "operatorKind": "chain", 
"operatorSyntax": "expr ?~> expr", "precedence": 5, "examples": [ 
"${header.name} ?~> ${trim()} ?~> ${uppercase()}" ] },
-    "?:": { "index": 33, "kind": "operator", "displayName": "Elvis", "label": 
"other", "required": false, "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Elvis operator 
(null-coalescing). Returns the left operand if it is not null\/empty, otherwise 
returns the right operand as a fallback value.", "operatorKind": "other", 
"operatorSyntax": "expr ?: defaultValue", "precedence": 20, "examples": [ 
"${header.username} ?: 'Guest'", "${body} ?: $ [...]
+    "?:": { "index": 33, "kind": "operator", "displayName": "Elvis", "label": 
"other", "required": false, "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Elvis operator 
(null-coalescing). Returns the left operand if it is not null\/empty, otherwise 
returns the right operand as a fallback value.", "operatorKind": "other", 
"operatorSyntax": "expr ?: defaultValue", "precedence": 20, "examples": [ 
"${header.username} ?: 'Guest'", "${body} ?: $ [...]
+    ":=": { "index": 34, "kind": "operator", "displayName": "Init variable", 
"label": "init", "required": false, "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Assigns a local variable 
in the init block at the top of an expression ($init{ ... }init$), computed 
once and used in the expression as ${name}. Each statement ends with a 
semicolon and a new line.", "operatorKind": "init", "operatorSyntax": "$name := 
expr;", "precedence": 1, "exam [...]
+    "~:=": { "index": 35, "kind": "operator", "displayName": "Init function", 
"label": "init", "required": false, "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Declares a local custom 
function in the init block at the top of an expression ($init{ ... }init$), 
usually as a chain of functions on the input, called as ${name()} (the message 
body as input), ${name(exp)} (an explicit input) or from another function as 
${function(name)}. Each s [...]
   }
 }
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/simple/camel-simple-validator.js
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/simple/camel-simple-validator.js
index fedf540459e7..091e88d267c2 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/simple/camel-simple-validator.js
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/simple/camel-simple-validator.js
@@ -192,7 +192,9 @@ const OPERATORS = {
   '? :': { kind: 'ternary', description: 'Ternary conditional operator. 
Evaluates the predicate and returns trueValue if true, falseValue if false. 
Requires spaces around both ? and : tokens.' },
   '~>': { kind: 'chain', description: 'Pipes the result of the left expression 
as input body to the right expression. Use $param in the right expression to 
reference the piped value explicitly.' },
   '?~>': { kind: 'chain', description: 'Null-safe chain operator. Same as ~> 
but stops chaining and returns null if the left expression evaluates to null.' 
},
-  '?:': { kind: 'other', description: 'Elvis operator (null-coalescing). 
Returns the left operand if it is not null/empty, otherwise returns the right 
operand as a fallback value.' }
+  '?:': { kind: 'other', description: 'Elvis operator (null-coalescing). 
Returns the left operand if it is not null/empty, otherwise returns the right 
operand as a fallback value.' },
+  ':=': { kind: 'init', description: 'Assigns a local variable in the init 
block at the top of an expression ($init{ ... }init$), computed once and used 
in the expression as ${name}. Each statement ends with a semicolon and a new 
line.' },
+  '~:=': { kind: 'init', description: 'Declares a local custom function in the 
init block at the top of an expression ($init{ ... }init$), usually as a chain 
of functions on the input, called as ${name()} (the message body as input), 
${name(exp)} (an explicit input) or from another function as ${function(name)}. 
Each statement ends with a semicolon and a new line.' }
 };
 
 
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 4e7580acfd4e..e2b8a01cf9be 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
@@ -213,6 +213,8 @@
     "? :": { "index": 30, "kind": "operator", "displayName": "Ternary", 
"label": "ternary", "required": false, "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "Ternary conditional 
operator. Evaluates the predicate and returns trueValue if true, falseValue if 
false. Requires spaces around both ? and : tokens.", "operatorKind": "ternary", 
"operatorSyntax": "predicate ? trueValue : falseValue", "precedence": 25, 
"examples": [ "${header.foo} >  [...]
     "~>": { "index": 31, "kind": "operator", "displayName": "Chain", "label": 
"chain", "required": false, "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Pipes the result of the 
left expression as input body to the right expression. Use $param in the right 
expression to reference the piped value explicitly.", "operatorKind": "chain", 
"operatorSyntax": "expr ~> expr", "precedence": 5, "examples": [ "${trim()} ~> 
${uppercase()}", "${substrin [...]
     "?~>": { "index": 32, "kind": "operator", "displayName": "Chain null 
safe", "label": "chain", "required": false, "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Null-safe chain operator. Same as ~> but stops chaining and returns null if 
the left expression evaluates to null.", "operatorKind": "chain", 
"operatorSyntax": "expr ?~> expr", "precedence": 5, "examples": [ 
"${header.name} ?~> ${trim()} ?~> ${uppercase()}" ] },
-    "?:": { "index": 33, "kind": "operator", "displayName": "Elvis", "label": 
"other", "required": false, "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Elvis operator 
(null-coalescing). Returns the left operand if it is not null\/empty, otherwise 
returns the right operand as a fallback value.", "operatorKind": "other", 
"operatorSyntax": "expr ?: defaultValue", "precedence": 20, "examples": [ 
"${header.username} ?: 'Guest'", "${body} ?: $ [...]
+    "?:": { "index": 33, "kind": "operator", "displayName": "Elvis", "label": 
"other", "required": false, "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Elvis operator 
(null-coalescing). Returns the left operand if it is not null\/empty, otherwise 
returns the right operand as a fallback value.", "operatorKind": "other", 
"operatorSyntax": "expr ?: defaultValue", "precedence": 20, "examples": [ 
"${header.username} ?: 'Guest'", "${body} ?: $ [...]
+    ":=": { "index": 34, "kind": "operator", "displayName": "Init variable", 
"label": "init", "required": false, "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Assigns a local variable 
in the init block at the top of an expression ($init{ ... }init$), computed 
once and used in the expression as ${name}. Each statement ends with a 
semicolon and a new line.", "operatorKind": "init", "operatorSyntax": "$name := 
expr;", "precedence": 1, "exam [...]
+    "~:=": { "index": 35, "kind": "operator", "displayName": "Init function", 
"label": "init", "required": false, "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Declares a local custom 
function in the init block at the top of an expression ($init{ ... }init$), 
usually as a chain of functions on the input, called as ${name()} (the message 
body as input), ${name(exp)} (an explicit input) or from another function as 
${function(name)}. Each s [...]
   }
 }
diff --git 
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc
 
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc
index 58c5c2057aed..8925f33c254f 100644
--- 
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc
+++ 
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc
@@ -160,9 +160,7 @@ and then refer to the file such as 
`resource:classpath:mymapping.txt` where the
 
 === Init Blocks with custom functions
 
-You can also declare custom functions using
-
-Inside the init block, it is a lso possible to define custom functions in the 
syntax `$nane ~:= <statement>;` where you can then use simple language to 
declare
+Inside the init block, it is also possible to define custom functions in the 
syntax `$name ~:= <statement>;` where you can then use simple language to 
declare
 the structure of the function. Then you can later use these custom functions 
in your simple language expressions.
 
 For example to create a function that can cleanup a `String` value:
diff --git 
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-operators.adoc
 
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-operators.adoc
index da22008cd82e..e5354e79f7db 100644
--- 
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-operators.adoc
+++ 
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-operators.adoc
@@ -115,6 +115,8 @@ The following other operators can be used:
 |`?:` | The elvis operator returns the left-hand side if it has an effective 
Boolean value of true, otherwise it returns the right-hand side. This is useful 
for providing fallback values when an expression may evaluate to a value with 
an effective Boolean value of false (such as `null`, `false`, `0`, empty/blank 
string).
 |`~>` | The chain operator is used in the situations where multiple nested 
functions need to be applied to a value, while making it easy to read. The 
value on the left-hand-side is evaluated, and set as the new message body 
before evaluating the right-hand-side function. This concept is similar to the 
xref:eips:pipeline-eip.adoc[Pipeline EIP].
 |`?~>` | The null-safe chain operator, where the chain will not continue when 
a function returned `null`.
+|`:=` | Assigns a local variable in the init block at the top of an expression 
(`$init{ $minAge := 18; }init$`), see 
xref:simple-advanced.adoc#_init_blocks[Init Blocks].
+|`~:=` | Declares a local custom function in the init block, usually as a 
chain (`$init{ $clean ~:= ${trim()} ~> ${uppercase()}; }init$`), called as 
`${clean()}`, see 
xref:simple-advanced.adoc#_init_blocks_with_custom_functions[Init Blocks with 
custom functions].
 |====
 
 ==== Ternary Operator
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleOperatorConstants.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleOperatorConstants.java
index 658878ea5778..6b1c8318dbf7 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleOperatorConstants.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleOperatorConstants.java
@@ -239,6 +239,26 @@ public final class SimpleOperatorConstants {
               annotations = { "kind=other", "syntax=expr ?: defaultValue", 
"precedence=20" })
     public static final String ELVIS = "?:";
 
+    // --- Init block operators (only inside $init{ ... }init$ at the start of 
an expression) ---
+
+    @Metadata(description = "Assigns a local variable in the init block at the 
top of an expression ($init{ ... }init$),"
+                            + " computed once and used in the expression as 
${name}. Each statement ends with a semicolon and a new line.",
+              label = "init",
+              examples = { "$init{\n  $minAge := 18;\n  $greeting := 'Hello 
${body}';\n}init$\n${greeting} (${minAge})" },
+              annotations = { "kind=init", "syntax=$name := expr;", 
"precedence=1" })
+    public static final String INIT_VARIABLE = ":=";
+
+    @Metadata(description = "Declares a local custom function in the init 
block at the top of an expression ($init{ ... }init$),"
+                            + " usually as a chain of functions on the input, 
called as ${name()} (the message body as input),"
+                            + " ${name(exp)} (an explicit input) or from 
another function as ${function(name)}."
+                            + " Each statement ends with a semicolon and a new 
line.",
+              label = "init",
+              examples = {
+                      "$init{\n  $cleanName ~:= ${trim()} ~> 
${normalizeWhitespace()} ~> ${uppercase()};\n}init$\nCustomer: 
${cleanName(${header.customerName})}",
+                      "$init{\n  $clean ~:= ${trim()} ~> 
${normalizeWhitespace()};\n  $count ~:= ${clean()} ~> ${split(' ')} ~> 
${size()};\n}init$\nYou said: ${clean()} in ${count()} words" },
+              annotations = { "kind=init", "syntax=$name ~:= expr ~> expr;", 
"precedence=1" })
+    public static final String INIT_FUNCTION = "~:=";
+
     private SimpleOperatorConstants() {
     }
 }
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 97cbf8277fbe..a715af758295 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
@@ -177,6 +177,21 @@ class CatalogDocsTest {
         assertTrue(binary.getInteger("matchedOperators") > 5);
         JsonObject eq = (JsonObject) 
binary.getCollection("operators").iterator().next();
         assertEquals("LHS == RHS", eq.getString("syntax"));
+
+        // CAMEL-24874: the init block operators are in the catalog, so a 
model asking about a local function or the
+        // init block gets the syntax and a complete example instead of the 
whole advanced page
+        JsonObject init = catalogDoc(Map.of("name", "simple", "kind", 
"language", "optionsFilter", "init"));
+        assertEquals("init", init.getString("operatorKind"));
+        assertEquals(2, init.getInteger("matchedOperators"));
+        JsonObject function = init.getCollection("operators").stream()
+                .map(JsonObject.class::cast)
+                .filter(op -> "~:=".equals(op.getString("name")))
+                .findFirst().orElseThrow();
+        assertEquals("$name ~:= expr ~> expr;", function.getString("syntax"));
+        String example = (String) 
function.getCollection("examples").iterator().next();
+        assertTrue(example.startsWith("$init{\n") && 
example.contains("}init$\n"), example);
+        JsonObject summary = catalogDoc(Map.of("name", "simple", "kind", 
"language"));
+        assertTrue(summary.getCollection("operatorSyntax").contains("$name := 
expr;"));
     }
 
     @Test

Reply via email to