Croway commented on code in PR #27839:
URL: https://github.com/apache/camel/pull/27839#discussion_r4296592178
##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java:
##########
@@ -103,11 +103,9 @@ static void register(Consumer<ToolDescriptor> registry) {
"common (default: no deprecated or advanced),
required, all or false", false)
.param("includeHeaders", "boolean", "Include the message
headers of a component (default false)", false)
.param("includeDoc", "boolean", "Include the full AsciiDoc
page (default false)", false)
- .param("docPage", "string", "simple doc sub-page to return as
text (functions, operators, ognl, advanced)",
- false)
- .param("optionsFilter", "string",
- "Keyword to match in option names or descriptions; for
an EIP whose own options do not match, the options of its elements are searched
(nestedOptions)",
- false)
+ .param("docPage", "string", CatalogDocs.DOC_PAGE_DESC, false)
+ .param("optionsFilter", "string",
CatalogDocs.OPTIONS_FILTER_DESC, false)
+ .param("limit", "integer", CatalogDocs.OPTIONS_LIMIT_DESC,
false)
Review Comment:
Measured again after rebasing on main: core ~5064 (budget 5000), core+groups
~5843 (5750), full ~10508 (10500), core+semantic ~6193 (6100). I tightened the
`docPage`, `optionsFilter` and `limit` descriptions and the full-key sentence
without dropping anything a model needs (~23 tokens saved), then raised the
budgets in `AiPanelPromptBudgetTest`, with a comment on each: core 5100
(measured ~5041), core+groups 5900 (~5820), full 10600 (~10485), core+semantic
6250 (~6170). Full+semantic is ~10822, under its 11000 limit, so that one is
unchanged. All 6 tests pass.
_Claude Code on behalf of Croway_
##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java:
##########
@@ -1434,9 +1651,396 @@ private static JsonObject componentDoc(
result.put("docPages", new JsonArray(docPages));
result.put("docPagesHint", pagesHint(docPages, "page"));
}
+ if (!how.sections().isEmpty()) {
+ // what the excerpt leaves out is often what decides correctness
(the mail useHeader* options, the xslt
+ // header parameters), so the page says what else it has, the
sections with the filter term first
+ JsonArray titles = sectionTitles(how.sections(), filter);
+ result.put("sections", titles);
+ int matching = filter != null ? sectionsContaining(how.sections(),
filter) : 0;
+ result.put("sectionsHint", "docPage=<section title> returns that
section of the page as text"
+ + (matching > 0
+ ? "; the first " + matching + "
contain '" + filter + "'"
+ : ""));
+ }
+ String mainGroup = MAIN_CONFIG_GROUPS.get(model.getScheme());
+ if (mainGroup != null) {
+ putMainConfig(result, how, mainGroup);
+ }
return result;
}
+ /**
+ * The options of a component: those the scope accepts and the filter
matches, the required ones first, then the
+ * endpoint options (what a URI takes) and the component options, with
their application.properties key. Without a
+ * filter only the first {@link #DEFAULT_OPTIONS_LIMIT} are listed in full
and the rest by name. A filter that
+ * matches nothing gets a note and the closest option names, and the
prefix options are always said, as they take
+ * keys no option list names (CAMEL-25541).
+ */
+ private static void putComponentOptions(
+ JsonObject result, ComponentModel model, ComponentAnswer how,
OptionScope scope) {
+ String filter = how.filter();
+ List<JsonObject> required = new ArrayList<>();
+ List<JsonObject> byName = new ArrayList<>();
+ List<JsonObject> others = new ArrayList<>();
+ int omitted = 0;
+ for (String optionScope : List.of("endpoint", "component")) {
+ List<? extends BaseOptionModel> options
+ = "endpoint".equals(optionScope) ?
model.getEndpointOptions() : model.getComponentOptions();
+ if (options == null) {
+ continue;
+ }
+ for (BaseOptionModel opt : options) {
+ if (!matchesOptionFilter(opt, filter)) {
+ continue;
+ }
+ if (!scope.accepts(opt, filter)) {
+ omitted++;
+ continue;
+ }
+ JsonObject o = optionToJson(opt, optionScope);
+ if ("component".equals(optionScope)) {
+ // the only valid application.properties key of an option:
camel.component.<scheme>.* sets a
+ // component option, never an endpoint one
+ o.put("configKey", "camel.component." + model.getScheme()
+ "." + opt.getName());
+ }
+ if (opt.isRequired()) {
+ required.add(o);
+ } else if (filter != null &&
opt.getName().toLowerCase(Locale.ROOT).contains(filter)) {
+ // a name match before a match in a description
+ byName.add(o);
+ } else {
+ others.add(o);
+ }
+ }
+ }
+ List<JsonObject> ordered = new ArrayList<>(required);
+ ordered.addAll(byName);
+ ordered.addAll(others);
+ int max = how.limit() >= 0 ? how.limit() : filter == null ?
DEFAULT_OPTIONS_LIMIT : Integer.MAX_VALUE;
+ // the required options are always listed
+ int listed = Math.min(ordered.size(), Math.max(max, required.size()));
+ putOptions(result, new JsonArray(ordered.subList(0, listed)), omitted,
scope);
+ result.put("matchedOptions", ordered.size());
+ if (listed < ordered.size()) {
+ Set<String> shown = new HashSet<>();
+ ordered.subList(0, listed).forEach(o ->
shown.add(o.getString("name")));
+ Set<String> rest = new LinkedHashSet<>();
+ for (JsonObject o : ordered.subList(listed, ordered.size())) {
+ if (!shown.contains(o.getString("name"))) {
+ rest.add(o.getString("name"));
+ }
Review Comment:
Done as you suggested. When an endpoint option has a component option with
the same name, it is listed once, with `scope: "endpoint,component"` and its
`configKey`. The component twin is no longer listed separately, so the `shown`
filtering is gone and `matchedOptions` counts each name once. The kafka default
answer now has `brokers` with `configKey: camel.component.kafka.brokers`. When
the endpoint twin is outside the requested scope (timer's `bridgeErrorHandler`
is advanced on the endpoint only), the component option is listed on its own
and is not counted as omitted. A new test checks kafka's `brokers` configKey
and that the listed and named options have no duplicates and add up to
`matchedOptions`. The upgrade guide and the MCP page describe the merged scope.
_Claude Code on behalf of Croway_
##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java:
##########
@@ -1434,9 +1651,396 @@ private static JsonObject componentDoc(
result.put("docPages", new JsonArray(docPages));
result.put("docPagesHint", pagesHint(docPages, "page"));
}
+ if (!how.sections().isEmpty()) {
+ // what the excerpt leaves out is often what decides correctness
(the mail useHeader* options, the xslt
+ // header parameters), so the page says what else it has, the
sections with the filter term first
+ JsonArray titles = sectionTitles(how.sections(), filter);
+ result.put("sections", titles);
+ int matching = filter != null ? sectionsContaining(how.sections(),
filter) : 0;
+ result.put("sectionsHint", "docPage=<section title> returns that
section of the page as text"
+ + (matching > 0
+ ? "; the first " + matching + "
contain '" + filter + "'"
+ : ""));
+ }
+ String mainGroup = MAIN_CONFIG_GROUPS.get(model.getScheme());
+ if (mainGroup != null) {
+ putMainConfig(result, how, mainGroup);
+ }
return result;
}
+ /**
+ * The options of a component: those the scope accepts and the filter
matches, the required ones first, then the
+ * endpoint options (what a URI takes) and the component options, with
their application.properties key. Without a
+ * filter only the first {@link #DEFAULT_OPTIONS_LIMIT} are listed in full
and the rest by name. A filter that
+ * matches nothing gets a note and the closest option names, and the
prefix options are always said, as they take
+ * keys no option list names (CAMEL-25541).
+ */
+ private static void putComponentOptions(
+ JsonObject result, ComponentModel model, ComponentAnswer how,
OptionScope scope) {
+ String filter = how.filter();
+ List<JsonObject> required = new ArrayList<>();
+ List<JsonObject> byName = new ArrayList<>();
+ List<JsonObject> others = new ArrayList<>();
+ int omitted = 0;
+ for (String optionScope : List.of("endpoint", "component")) {
+ List<? extends BaseOptionModel> options
+ = "endpoint".equals(optionScope) ?
model.getEndpointOptions() : model.getComponentOptions();
+ if (options == null) {
+ continue;
+ }
+ for (BaseOptionModel opt : options) {
+ if (!matchesOptionFilter(opt, filter)) {
+ continue;
+ }
+ if (!scope.accepts(opt, filter)) {
+ omitted++;
+ continue;
+ }
+ JsonObject o = optionToJson(opt, optionScope);
+ if ("component".equals(optionScope)) {
+ // the only valid application.properties key of an option:
camel.component.<scheme>.* sets a
+ // component option, never an endpoint one
+ o.put("configKey", "camel.component." + model.getScheme()
+ "." + opt.getName());
+ }
+ if (opt.isRequired()) {
+ required.add(o);
+ } else if (filter != null &&
opt.getName().toLowerCase(Locale.ROOT).contains(filter)) {
+ // a name match before a match in a description
+ byName.add(o);
+ } else {
+ others.add(o);
+ }
+ }
+ }
+ List<JsonObject> ordered = new ArrayList<>(required);
+ ordered.addAll(byName);
+ ordered.addAll(others);
+ int max = how.limit() >= 0 ? how.limit() : filter == null ?
DEFAULT_OPTIONS_LIMIT : Integer.MAX_VALUE;
+ // the required options are always listed
+ int listed = Math.min(ordered.size(), Math.max(max, required.size()));
+ putOptions(result, new JsonArray(ordered.subList(0, listed)), omitted,
scope);
+ result.put("matchedOptions", ordered.size());
+ if (listed < ordered.size()) {
+ Set<String> shown = new HashSet<>();
+ ordered.subList(0, listed).forEach(o ->
shown.add(o.getString("name")));
+ Set<String> rest = new LinkedHashSet<>();
+ for (JsonObject o : ordered.subList(listed, ordered.size())) {
+ if (!shown.contains(o.getString("name"))) {
+ rest.add(o.getString("name"));
+ }
+ }
+ result.put("truncated", true);
+ result.put("listedOptions", listed);
+ result.put("moreOptions", new JsonArray(new ArrayList<>(rest)));
+ String hint = listed + " of " + ordered.size() + " options listed
in full (required first, then the"
+ + " endpoint options), the others by name in
moreOptions; optionsFilter=<word or option name>"
+ + " returns the matching ones in full,
includeOptions=all with limit=<n> lists more";
+ String omittedHint = result.getString("optionsHint");
+ result.put("optionsHint", omittedHint != null ? hint + "; " +
omittedHint : hint);
+ }
+ if (filter != null && ordered.isEmpty() && omitted == 0) {
+ // an empty list read as "not supported" (smtp + tls, xslt +
param): say that nothing matched, and where
+ // else to look
+ result.put("note", "no option matches '" + filter + "': see
closestOptions, prefixOptions and the"
+ + " sections of the page below
(docPage=<section title> returns one)");
+ JsonArray closest = closestOptions(model, filter);
+ if (!closest.isEmpty()) {
+ result.put("closestOptions", closest);
+ }
+ }
+ JsonArray prefixOptions = prefixOptions(model);
+ if (!prefixOptions.isEmpty()) {
+ result.put("prefixOptions", prefixOptions);
+ }
+ }
+
+ /**
+ * The options whose name, or a word of it, is closest to the filter by
edit distance, with their group: ssl for a
+ * mistyped tsl.
+ */
+ private static JsonArray closestOptions(ComponentModel model, String
filter) {
+ Map<String, List<BaseOptionModel>> byWord = new LinkedHashMap<>();
+ Map<BaseOptionModel, String> scopes = new LinkedHashMap<>();
+ for (String optionScope : List.of("endpoint", "component")) {
+ List<? extends BaseOptionModel> options
+ = "endpoint".equals(optionScope) ?
model.getEndpointOptions() : model.getComponentOptions();
+ if (options == null) {
+ continue;
+ }
+ for (BaseOptionModel opt : options) {
+ scopes.putIfAbsent(opt, optionScope);
+ byWord.computeIfAbsent(opt.getName().toLowerCase(Locale.ROOT),
k -> new ArrayList<>()).add(opt);
+ for (String word :
opt.getName().split("(?<=[a-z0-9])(?=[A-Z])")) {
+ if (word.length() > 1) {
+ byWord.computeIfAbsent(word.toLowerCase(Locale.ROOT),
k -> new ArrayList<>()).add(opt);
+ }
+ }
+ }
+ }
+ JsonArray answer = new JsonArray();
+ Set<String> seen = new HashSet<>();
+ for (String word :
EditDistanceSuggestionStrategy.suggest(byWord.keySet(), filter, 5)) {
+ for (BaseOptionModel opt : byWord.get(word)) {
+ if (answer.size() < 5 && seen.add(opt.getName())) {
+ JsonObject o = new JsonObject();
+ o.put("name", opt.getName());
+ if (opt.getGroup() != null) {
+ o.put("group", opt.getGroup());
+ }
+ o.put("scope", scopes.get(opt));
+ answer.add(o);
+ }
+ }
+ }
+ return answer;
+ }
+
+ /**
+ * The multi-value options of a component, which take every URI parameter
with their prefix: any mail.* parameter of
+ * smtp goes to JavaMail (mail.smtp.starttls.enable=true), which no option
is named after.
+ */
+ private static JsonArray prefixOptions(ComponentModel model) {
+ JsonArray answer = new JsonArray();
+ Set<String> seen = new HashSet<>();
+ if (model.getEndpointOptions() == null) {
+ return answer;
+ }
+ for (BaseOptionModel opt : model.getEndpointOptions()) {
+ if (opt.isMultiValue() && opt.getPrefix() != null &&
seen.add(opt.getName())) {
+ JsonObject o = new JsonObject();
+ o.put("name", opt.getName());
+ o.put("prefix", opt.getPrefix());
+ o.put("usage", "any " + opt.getPrefix() + "* URI parameter is
passed on as " + opt.getName() + ": ?"
+ + opt.getPrefix() + "<key>=<value>");
+ answer.add(o);
+ }
+ }
+ return answer;
+ }
+
+ /**
+ * Where a component that runs on a server of camel run or Camel Main gets
its settings: the HTTP port of
+ * platform-http is camel.server.port, which an agent searched the
component's options for (CAMEL-25541).
+ */
+ private static void putMainConfig(JsonObject result, ComponentAnswer how,
String group) {
+ result.put("mainConfig", "with camel run or Camel Main the HTTP server
is configured by the " + group
+ + ".* keys of application.properties, not by
an option of this component:"
+ + " camel.server.enabled=true and
camel.server.port=8080 (camel run --port=8080 sets"
+ + " both); camel_catalog_doc name=" + group +
" lists the keys");
Review Comment:
I checked the code: `DependencyDownloaderComponentResolver` calls
`MainHttpServerFactory.setupHttpServer` for `PlatformHttpComponent`, and
`configureHttpServer` applies `camel.server.*` and skips `enabled`.
`mainConfig` now reads: "... with camel run it starts when a route uses
platform-http, on port 8080 (camel.server.port or camel run --port changes it);
with Camel Main set camel.server.enabled=true and camel.server.port; ...". The
test checks both halves.
_Claude Code on behalf of Croway_
##########
components/camel-platform-http/src/main/docs/platform-http-component.adoc:
##########
@@ -40,6 +40,11 @@ include::partial$component-endpoint-headers.adoc[]
To use Platform HTTP, a provider (engine) is required to be available on the
classpath.
The purpose is to have drivers for different runtimes such as Quarkus, or
Spring Boot.
+With Camel Main and `camel run`, the provider is the embedded HTTP server of
`camel-platform-http-main`,
+which is disabled by default. Enable it and set its port in
`application.properties` with
+`camel.server.enabled=true` and `camel.server.port=8080` (`camel run
--port=8080` sets both).
+See xref:others:platform-http-main.adoc[Platform HTTP Main] for the other
`camel.server.*` options.
Review Comment:
Rewritten along your wording. With `camel run`, the server of
`camel-platform-http-main` starts when a route uses platform-http, on port 8080
(`camel.server.port` or `camel run --port` changes it). With Camel Main it is
disabled by default: enable it with `camel.server.enabled=true` and set
`camel.server.port`. The catalog mirror is regenerated and is byte-identical to
the source page.
_Claude Code on behalf of Croway_
##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java:
##########
@@ -1434,9 +1651,396 @@ private static JsonObject componentDoc(
result.put("docPages", new JsonArray(docPages));
result.put("docPagesHint", pagesHint(docPages, "page"));
}
+ if (!how.sections().isEmpty()) {
+ // what the excerpt leaves out is often what decides correctness
(the mail useHeader* options, the xslt
+ // header parameters), so the page says what else it has, the
sections with the filter term first
+ JsonArray titles = sectionTitles(how.sections(), filter);
+ result.put("sections", titles);
+ int matching = filter != null ? sectionsContaining(how.sections(),
filter) : 0;
+ result.put("sectionsHint", "docPage=<section title> returns that
section of the page as text"
+ + (matching > 0
+ ? "; the first " + matching + "
contain '" + filter + "'"
+ : ""));
+ }
+ String mainGroup = MAIN_CONFIG_GROUPS.get(model.getScheme());
+ if (mainGroup != null) {
+ putMainConfig(result, how, mainGroup);
+ }
return result;
}
+ /**
+ * The options of a component: those the scope accepts and the filter
matches, the required ones first, then the
+ * endpoint options (what a URI takes) and the component options, with
their application.properties key. Without a
+ * filter only the first {@link #DEFAULT_OPTIONS_LIMIT} are listed in full
and the rest by name. A filter that
+ * matches nothing gets a note and the closest option names, and the
prefix options are always said, as they take
+ * keys no option list names (CAMEL-25541).
+ */
+ private static void putComponentOptions(
+ JsonObject result, ComponentModel model, ComponentAnswer how,
OptionScope scope) {
+ String filter = how.filter();
+ List<JsonObject> required = new ArrayList<>();
+ List<JsonObject> byName = new ArrayList<>();
+ List<JsonObject> others = new ArrayList<>();
+ int omitted = 0;
+ for (String optionScope : List.of("endpoint", "component")) {
+ List<? extends BaseOptionModel> options
+ = "endpoint".equals(optionScope) ?
model.getEndpointOptions() : model.getComponentOptions();
+ if (options == null) {
+ continue;
+ }
+ for (BaseOptionModel opt : options) {
+ if (!matchesOptionFilter(opt, filter)) {
+ continue;
+ }
+ if (!scope.accepts(opt, filter)) {
+ omitted++;
+ continue;
+ }
+ JsonObject o = optionToJson(opt, optionScope);
+ if ("component".equals(optionScope)) {
+ // the only valid application.properties key of an option:
camel.component.<scheme>.* sets a
+ // component option, never an endpoint one
+ o.put("configKey", "camel.component." + model.getScheme()
+ "." + opt.getName());
+ }
+ if (opt.isRequired()) {
+ required.add(o);
+ } else if (filter != null &&
opt.getName().toLowerCase(Locale.ROOT).contains(filter)) {
+ // a name match before a match in a description
+ byName.add(o);
+ } else {
+ others.add(o);
+ }
+ }
+ }
+ List<JsonObject> ordered = new ArrayList<>(required);
+ ordered.addAll(byName);
+ ordered.addAll(others);
+ int max = how.limit() >= 0 ? how.limit() : filter == null ?
DEFAULT_OPTIONS_LIMIT : Integer.MAX_VALUE;
+ // the required options are always listed
+ int listed = Math.min(ordered.size(), Math.max(max, required.size()));
+ putOptions(result, new JsonArray(ordered.subList(0, listed)), omitted,
scope);
+ result.put("matchedOptions", ordered.size());
+ if (listed < ordered.size()) {
+ Set<String> shown = new HashSet<>();
+ ordered.subList(0, listed).forEach(o ->
shown.add(o.getString("name")));
+ Set<String> rest = new LinkedHashSet<>();
+ for (JsonObject o : ordered.subList(listed, ordered.size())) {
+ if (!shown.contains(o.getString("name"))) {
+ rest.add(o.getString("name"));
+ }
+ }
+ result.put("truncated", true);
+ result.put("listedOptions", listed);
+ result.put("moreOptions", new JsonArray(new ArrayList<>(rest)));
+ String hint = listed + " of " + ordered.size() + " options listed
in full (required first, then the"
+ + " endpoint options), the others by name in
moreOptions; optionsFilter=<word or option name>"
+ + " returns the matching ones in full,
includeOptions=all with limit=<n> lists more";
+ String omittedHint = result.getString("optionsHint");
+ result.put("optionsHint", omittedHint != null ? hint + "; " +
omittedHint : hint);
+ }
+ if (filter != null && ordered.isEmpty() && omitted == 0) {
+ // an empty list read as "not supported" (smtp + tls, xslt +
param): say that nothing matched, and where
+ // else to look
+ result.put("note", "no option matches '" + filter + "': see
closestOptions, prefixOptions and the"
+ + " sections of the page below
(docPage=<section title> returns one)");
+ JsonArray closest = closestOptions(model, filter);
+ if (!closest.isEmpty()) {
+ result.put("closestOptions", closest);
+ }
+ }
+ JsonArray prefixOptions = prefixOptions(model);
+ if (!prefixOptions.isEmpty()) {
+ result.put("prefixOptions", prefixOptions);
+ }
+ }
+
+ /**
+ * The options whose name, or a word of it, is closest to the filter by
edit distance, with their group: ssl for a
+ * mistyped tsl.
+ */
+ private static JsonArray closestOptions(ComponentModel model, String
filter) {
+ Map<String, List<BaseOptionModel>> byWord = new LinkedHashMap<>();
+ Map<BaseOptionModel, String> scopes = new LinkedHashMap<>();
+ for (String optionScope : List.of("endpoint", "component")) {
+ List<? extends BaseOptionModel> options
+ = "endpoint".equals(optionScope) ?
model.getEndpointOptions() : model.getComponentOptions();
+ if (options == null) {
+ continue;
+ }
+ for (BaseOptionModel opt : options) {
+ scopes.putIfAbsent(opt, optionScope);
+ byWord.computeIfAbsent(opt.getName().toLowerCase(Locale.ROOT),
k -> new ArrayList<>()).add(opt);
+ for (String word :
opt.getName().split("(?<=[a-z0-9])(?=[A-Z])")) {
+ if (word.length() > 1) {
+ byWord.computeIfAbsent(word.toLowerCase(Locale.ROOT),
k -> new ArrayList<>()).add(opt);
+ }
+ }
+ }
+ }
+ JsonArray answer = new JsonArray();
+ Set<String> seen = new HashSet<>();
+ for (String word :
EditDistanceSuggestionStrategy.suggest(byWord.keySet(), filter, 5)) {
+ for (BaseOptionModel opt : byWord.get(word)) {
+ if (answer.size() < 5 && seen.add(opt.getName())) {
+ JsonObject o = new JsonObject();
+ o.put("name", opt.getName());
+ if (opt.getGroup() != null) {
+ o.put("group", opt.getGroup());
+ }
+ o.put("scope", scopes.get(opt));
+ answer.add(o);
+ }
+ }
+ }
+ return answer;
+ }
+
+ /**
+ * The multi-value options of a component, which take every URI parameter
with their prefix: any mail.* parameter of
+ * smtp goes to JavaMail (mail.smtp.starttls.enable=true), which no option
is named after.
+ */
+ private static JsonArray prefixOptions(ComponentModel model) {
+ JsonArray answer = new JsonArray();
+ Set<String> seen = new HashSet<>();
+ if (model.getEndpointOptions() == null) {
+ return answer;
+ }
+ for (BaseOptionModel opt : model.getEndpointOptions()) {
+ if (opt.isMultiValue() && opt.getPrefix() != null &&
seen.add(opt.getName())) {
+ JsonObject o = new JsonObject();
+ o.put("name", opt.getName());
+ o.put("prefix", opt.getPrefix());
+ o.put("usage", "any " + opt.getPrefix() + "* URI parameter is
passed on as " + opt.getName() + ": ?"
+ + opt.getPrefix() + "<key>=<value>");
+ answer.add(o);
+ }
+ }
+ return answer;
+ }
+
+ /**
+ * Where a component that runs on a server of camel run or Camel Main gets
its settings: the HTTP port of
+ * platform-http is camel.server.port, which an agent searched the
component's options for (CAMEL-25541).
+ */
+ private static void putMainConfig(JsonObject result, ComponentAnswer how,
String group) {
+ result.put("mainConfig", "with camel run or Camel Main the HTTP server
is configured by the " + group
+ + ".* keys of application.properties, not by
an option of this component:"
+ + " camel.server.enabled=true and
camel.server.port=8080 (camel run --port=8080 sets"
+ + " both); camel_catalog_doc name=" + group +
" lists the keys");
+ if (how.filter() == null) {
+ return;
+ }
+ JsonArray keys = new JsonArray();
+ try {
+ for (BaseOptionModel o :
optionsUnder(how.catalog().mainModel().getOptions(), group)) {
+ if (matchesOptionFilter(o, how.filter())) {
+ keys.add(mainOptionToJson(o));
+ }
+ }
+ } catch (Exception e) {
+ return;
+ }
+ if (!keys.isEmpty()) {
+ result.put("mainOptions", keys);
+ }
+ }
+
+ /**
+ * The name the documentation page of a component has: its scheme, else
the artifact (smtp, imap and pop3 are on the
+ * mail page of camel-mail); null when there is none.
+ */
+ private static String componentDocName(CamelCatalog catalog,
ComponentModel model) {
+ if (catalog.asciiDoc(model.getScheme() + "-component") != null) {
+ return model.getScheme();
+ }
+ String artifact = model.getArtifactId();
+ if (artifact != null && artifact.startsWith("camel-")) {
+ String name = artifact.substring("camel-".length());
+ if (catalog.asciiDoc(name + "-component") != null) {
+ return name;
+ }
+ }
+ return null;
+ }
+
+ private static String titleWords(String text) {
+ return text.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]+", "
").strip();
+ }
+
+ /**
+ * A section of an AsciiDoc page: its title, its level (2 for ==), its
text with its subsections, and its own text
+ * up to its first subsection.
+ */
+ record DocSection(String title, int level, String text, String own) {
+ }
+
+ private static final Pattern HEADING = Pattern.compile("^(={2,4})
+(\\S.*)$");
+
+ /** The sections of a page, from == to ====, in the order of the page;
empty when there is no page. */
+ static List<DocSection> docSections(String adoc) {
+ List<DocSection> answer = new ArrayList<>();
+ if (adoc == null) {
+ return answer;
+ }
+ String[] lines = adoc.split("\n", -1);
+ List<Integer> starts = new ArrayList<>();
+ List<Integer> levels = new ArrayList<>();
+ List<String> titles = new ArrayList<>();
+ boolean fenced = false;
+ for (int i = 0; i < lines.length; i++) {
+ String trimmed = lines[i].strip();
+ if (trimmed.startsWith("----") || trimmed.equals("....")) {
Review Comment:
Fixed. `docSections` now records the delimiter that opened a block and
closes the block only on that same delimiter. Listing (`----`), literal
(`....`), comment (`////`) and passthrough (`++++`) blocks are covered. A new
test has a `....` block inside a `----` block and a `////` comment block, and
the headings inside them are not sections. (The suggested patch would still
have closed the outer block on `....`.)
_Claude Code on behalf of Croway_
##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java:
##########
@@ -250,6 +309,10 @@ public static JsonObject catalogDoc(
// (jackson): both are answered, with the YAML shape in the answer
(CAMEL-24898)
String artifact =
YAML_DATAFORMAT_NAMES.getOrDefault(name.toLowerCase(), name);
DataFormatModel dm = catalog.dataFormatModel(artifact);
+ if (dm == null && ignoringCase(catalog.findDataFormatNames(),
artifact) != null) {
+ artifact = ignoringCase(catalog.findDataFormatNames(),
artifact);
Review Comment:
Fixed: the result is computed once into a local.
_Claude Code on behalf of Croway_
--
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]