You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ap...@apache.org on 2024/09/04 13:28:42 UTC
(ignite-3) branch main updated: IGNITE-23108 Improve HOCON highlighting in CLI (#4323)
This is an automated email from the ASF dual-hosted git repository.
apkhmv pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 118c7fb488 IGNITE-23108 Improve HOCON highlighting in CLI (#4323)
118c7fb488 is described below
commit 118c7fb488687adfc56e3cc2183f607281277c3d
Author: Vadim Pakhnushev <86...@users.noreply.github.com>
AuthorDate: Wed Sep 4 16:28:36 2024 +0300
IGNITE-23108 Improve HOCON highlighting in CLI (#4323)
---
gradle/libs.versions.toml | 8 ++-
modules/cli/build.gradle | 1 +
.../java/org/apache/ignite/internal/cli/Main.java | 1 +
.../ignite/internal/cli/commands/FormatMixin.java | 68 ++++++++++++++++++
.../ignite/internal/cli/commands/Options.java | 4 ++
.../cluster/config/ClusterConfigShowCommand.java | 16 ++---
.../config/ClusterConfigShowReplCommand.java | 14 ++--
.../node/config/NodeConfigShowCommand.java | 14 ++--
.../node/config/NodeConfigShowReplCommand.java | 14 ++--
...iHighlighter.java => HoconAnsiHighlighter.java} | 48 +++++++------
.../highlighter/JsonAnsiHighlighter.java | 1 -
.../commands/treesitter/parser/HoconTokenType.java | 83 +++++++++++++++++++++
.../cli/commands/treesitter/parser/Indexer.java | 75 +++++++++++--------
.../cli/commands/treesitter/parser/Parser.java | 31 ++++++--
.../cli/core/repl/executor/ReplExecutorImpl.java | 1 +
.../{JsonDecorator.java => HoconDecorator.java} | 10 +--
.../internal/cli/decorators/JsonDecorator.java | 18 ++---
...siHighlighterTest.java => HighlighterTest.java} | 28 +++++---
.../treesitter/parser/HoconIndexerTest.java | 84 ++++++++++++++++++++++
19 files changed, 392 insertions(+), 127 deletions(-)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 03ceb99766..da05f8a7e1 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -79,9 +79,10 @@ awaitility = "4.2.2"
progressBar = "0.9.4"
guava = "33.2.1-jre"
jna = "5.14.0"
-tree-sitter = "0.22.5"
-tree-sitter-json = "0.21.0"
-tree-sitter-sql = "gh-pages"
+tree-sitter = "0.23.0"
+tree-sitter-json = "0.21.0a"
+tree-sitter-sql = "gh-pages-a"
+tree-sitter-hocon = "master-a"
#Tools
pmdTool = "6.55.0"
@@ -265,3 +266,4 @@ jna = { module = "net.java.dev.jna:jna", version.ref = "jna"}
tree-sitter = { module = "io.github.bonede:tree-sitter", version.ref = "tree-sitter" }
tree-sitter-json = { module = "io.github.bonede:tree-sitter-json", version.ref = "tree-sitter-json" }
tree-sitter-sql = { module = "io.github.bonede:tree-sitter-sql", version.ref = "tree-sitter-sql" }
+tree-sitter-hocon = { module = "io.github.bonede:tree-sitter-hocon", version.ref = "tree-sitter-hocon" }
diff --git a/modules/cli/build.gradle b/modules/cli/build.gradle
index f48bd28f01..08f96420f6 100644
--- a/modules/cli/build.gradle
+++ b/modules/cli/build.gradle
@@ -47,6 +47,7 @@ dependencies {
implementation libs.tree.sitter
implementation libs.tree.sitter.sql
implementation libs.tree.sitter.json
+ implementation libs.tree.sitter.hocon
implementation libs.fliptables
implementation libs.jline.core
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/Main.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/Main.java
index dbb6f2947f..80ec29669a 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/Main.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/Main.java
@@ -97,6 +97,7 @@ public class Main {
cmd.setExecutionExceptionHandler(new PicocliExecutionExceptionHandler());
cmd.setDefaultValueProvider(micronautFactory.create(ConfigDefaultValueProvider.class));
cmd.setTrimQuotes(true);
+ cmd.setCaseInsensitiveEnumValuesAllowed(true);
return cmd.execute(args);
}
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/FormatMixin.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/FormatMixin.java
new file mode 100644
index 0000000000..d6f965aec0
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/FormatMixin.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cli.commands;
+
+import static org.apache.ignite.internal.cli.commands.Options.Constants.CONFIG_FORMAT_OPTION;
+import static org.apache.ignite.internal.cli.commands.Options.Constants.CONFIG_FORMAT_OPTION_DESC;
+import static picocli.CommandLine.Help.Visibility.ALWAYS;
+
+import jakarta.inject.Inject;
+import org.apache.ignite.internal.cli.call.configuration.JsonString;
+import org.apache.ignite.internal.cli.config.CliConfigKeys;
+import org.apache.ignite.internal.cli.config.ConfigManagerProvider;
+import org.apache.ignite.internal.cli.core.decorator.Decorator;
+import org.apache.ignite.internal.cli.core.decorator.TerminalOutput;
+import org.apache.ignite.internal.cli.decorators.HoconDecorator;
+import org.apache.ignite.internal.cli.decorators.JsonDecorator;
+import picocli.CommandLine.Option;
+
+/** Mixin for config format option. */
+public class FormatMixin {
+ private enum Format {
+ JSON,
+ HOCON
+ }
+
+ @Option(
+ names = CONFIG_FORMAT_OPTION,
+ description = CONFIG_FORMAT_OPTION_DESC,
+ defaultValue = "HOCON",
+ showDefaultValue = ALWAYS
+ )
+ private Format format;
+
+ @Inject
+ private ConfigManagerProvider configManagerProvider;
+
+ /**
+ * Gets the decorator corresponding to the format.
+ *
+ * @return Decorator.
+ */
+ public Decorator<JsonString, TerminalOutput> decorator() {
+ if (format == Format.JSON) {
+ return new JsonDecorator(isHighlightEnabled());
+ }
+ // HOCON is default
+ return new HoconDecorator(isHighlightEnabled());
+ }
+
+ private boolean isHighlightEnabled() {
+ return Boolean.parseBoolean(configManagerProvider.get().getCurrentProperty(CliConfigKeys.SYNTAX_HIGHLIGHTING.value()));
+ }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/Options.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/Options.java
index 94afff0d6b..c3d1c61025 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/Options.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/Options.java
@@ -291,5 +291,9 @@ public enum Options {
public static final String RECOVERY_NODE_NAMES_OPTION_DESC = "Names specifying nodes to get partition states from. "
+ "Case-sensitive, without quotes, all nodes if not set";
+
+ public static final String CONFIG_FORMAT_OPTION = "--format";
+
+ public static final String CONFIG_FORMAT_OPTION_DESC = "Output format. Valid values: ${COMPLETION-CANDIDATES}";
}
}
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/config/ClusterConfigShowCommand.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/config/ClusterConfigShowCommand.java
index 0b8f1334af..4ba949887d 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/config/ClusterConfigShowCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/config/ClusterConfigShowCommand.java
@@ -22,12 +22,10 @@ import java.util.concurrent.Callable;
import org.apache.ignite.internal.cli.call.configuration.ClusterConfigShowCall;
import org.apache.ignite.internal.cli.call.configuration.ClusterConfigShowCallInput;
import org.apache.ignite.internal.cli.commands.BaseCommand;
+import org.apache.ignite.internal.cli.commands.FormatMixin;
import org.apache.ignite.internal.cli.commands.cluster.ClusterUrlProfileMixin;
-import org.apache.ignite.internal.cli.config.CliConfigKeys;
-import org.apache.ignite.internal.cli.config.ConfigManagerProvider;
import org.apache.ignite.internal.cli.core.call.CallExecutionPipeline;
import org.apache.ignite.internal.cli.core.exception.handler.ClusterNotInitializedExceptionHandler;
-import org.apache.ignite.internal.cli.decorators.JsonDecorator;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Parameters;
@@ -47,11 +45,11 @@ public class ClusterConfigShowCommand extends BaseCommand implements Callable<In
@Parameters(arity = "0..1", description = "Configuration path selector")
private String selector;
- @Inject
- private ClusterConfigShowCall call;
+ @Mixin
+ private FormatMixin format;
@Inject
- private ConfigManagerProvider configManagerProvider;
+ private ClusterConfigShowCall call;
/** {@inheritDoc} */
@Override
@@ -60,17 +58,13 @@ public class ClusterConfigShowCommand extends BaseCommand implements Callable<In
.inputProvider(this::buildCallInput)
.output(spec.commandLine().getOut())
.errOutput(spec.commandLine().getErr())
- .decorator(new JsonDecorator(isHighlightEnabled()))
+ .decorator(format.decorator())
.exceptionHandler(ClusterNotInitializedExceptionHandler.createHandler("Cannot show cluster config"))
.verbose(verbose)
.build()
.runPipeline();
}
- private boolean isHighlightEnabled() {
- return Boolean.parseBoolean(configManagerProvider.get().getCurrentProperty(CliConfigKeys.SYNTAX_HIGHLIGHTING.value()));
- }
-
private ClusterConfigShowCallInput buildCallInput() {
return ClusterConfigShowCallInput.builder()
.clusterUrl(clusterUrl.getClusterUrl())
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/config/ClusterConfigShowReplCommand.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/config/ClusterConfigShowReplCommand.java
index c606ef1a07..f9eda6a7ac 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/config/ClusterConfigShowReplCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/config/ClusterConfigShowReplCommand.java
@@ -21,13 +21,11 @@ import jakarta.inject.Inject;
import org.apache.ignite.internal.cli.call.configuration.ClusterConfigShowCall;
import org.apache.ignite.internal.cli.call.configuration.ClusterConfigShowCallInput;
import org.apache.ignite.internal.cli.commands.BaseCommand;
+import org.apache.ignite.internal.cli.commands.FormatMixin;
import org.apache.ignite.internal.cli.commands.cluster.ClusterUrlMixin;
import org.apache.ignite.internal.cli.commands.questions.ConnectToClusterQuestion;
-import org.apache.ignite.internal.cli.config.CliConfigKeys;
-import org.apache.ignite.internal.cli.config.ConfigManagerProvider;
import org.apache.ignite.internal.cli.core.exception.handler.ClusterNotInitializedExceptionHandler;
import org.apache.ignite.internal.cli.core.flow.builder.Flows;
-import org.apache.ignite.internal.cli.decorators.JsonDecorator;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Parameters;
@@ -55,8 +53,8 @@ public class ClusterConfigShowReplCommand extends BaseCommand implements Runnabl
@Inject
private ConnectToClusterQuestion question;
- @Inject
- private ConfigManagerProvider configManagerProvider;
+ @Mixin
+ private FormatMixin format;
@Override
public void run() {
@@ -65,15 +63,11 @@ public class ClusterConfigShowReplCommand extends BaseCommand implements Runnabl
.then(Flows.fromCall(call))
.exceptionHandler(ClusterNotInitializedExceptionHandler.createReplHandler("Cannot show cluster config"))
.verbose(verbose)
- .print(new JsonDecorator(isHighlightEnabled()))
+ .print(format.decorator())
.start();
}
private ClusterConfigShowCallInput configShowCallInput(String clusterUrl) {
return ClusterConfigShowCallInput.builder().selector(selector).clusterUrl(clusterUrl).build();
}
-
- private boolean isHighlightEnabled() {
- return Boolean.parseBoolean(configManagerProvider.get().getCurrentProperty(CliConfigKeys.SYNTAX_HIGHLIGHTING.value()));
- }
}
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/config/NodeConfigShowCommand.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/config/NodeConfigShowCommand.java
index 4031fd5f57..fae17ff388 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/config/NodeConfigShowCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/config/NodeConfigShowCommand.java
@@ -22,11 +22,9 @@ import java.util.concurrent.Callable;
import org.apache.ignite.internal.cli.call.configuration.NodeConfigShowCall;
import org.apache.ignite.internal.cli.call.configuration.NodeConfigShowCallInput;
import org.apache.ignite.internal.cli.commands.BaseCommand;
+import org.apache.ignite.internal.cli.commands.FormatMixin;
import org.apache.ignite.internal.cli.commands.node.NodeUrlProfileMixin;
-import org.apache.ignite.internal.cli.config.CliConfigKeys;
-import org.apache.ignite.internal.cli.config.ConfigManagerProvider;
import org.apache.ignite.internal.cli.core.call.CallExecutionPipeline;
-import org.apache.ignite.internal.cli.decorators.JsonDecorator;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Parameters;
@@ -47,8 +45,8 @@ public class NodeConfigShowCommand extends BaseCommand implements Callable<Integ
@Inject
private NodeConfigShowCall call;
- @Inject
- private ConfigManagerProvider configManagerProvider;
+ @Mixin
+ private FormatMixin format;
/** {@inheritDoc} */
@Override
@@ -57,16 +55,12 @@ public class NodeConfigShowCommand extends BaseCommand implements Callable<Integ
.inputProvider(this::buildCallInput)
.output(spec.commandLine().getOut())
.errOutput(spec.commandLine().getErr())
- .decorator(new JsonDecorator(isHighlightEnabled()))
+ .decorator(format.decorator())
.verbose(verbose)
.build()
.runPipeline();
}
- private boolean isHighlightEnabled() {
- return Boolean.parseBoolean(configManagerProvider.get().getCurrentProperty(CliConfigKeys.SYNTAX_HIGHLIGHTING.value()));
- }
-
private NodeConfigShowCallInput buildCallInput() {
return NodeConfigShowCallInput.builder()
.nodeUrl(nodeUrl.getNodeUrl())
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/config/NodeConfigShowReplCommand.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/config/NodeConfigShowReplCommand.java
index 365076bc73..90496bd2db 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/config/NodeConfigShowReplCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/config/NodeConfigShowReplCommand.java
@@ -21,12 +21,10 @@ import jakarta.inject.Inject;
import org.apache.ignite.internal.cli.call.configuration.NodeConfigShowCall;
import org.apache.ignite.internal.cli.call.configuration.NodeConfigShowCallInput;
import org.apache.ignite.internal.cli.commands.BaseCommand;
+import org.apache.ignite.internal.cli.commands.FormatMixin;
import org.apache.ignite.internal.cli.commands.node.NodeUrlMixin;
import org.apache.ignite.internal.cli.commands.questions.ConnectToClusterQuestion;
-import org.apache.ignite.internal.cli.config.CliConfigKeys;
-import org.apache.ignite.internal.cli.config.ConfigManagerProvider;
import org.apache.ignite.internal.cli.core.flow.builder.Flows;
-import org.apache.ignite.internal.cli.decorators.JsonDecorator;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Parameters;
@@ -50,8 +48,8 @@ public class NodeConfigShowReplCommand extends BaseCommand implements Runnable {
@Inject
private ConnectToClusterQuestion question;
- @Inject
- private ConfigManagerProvider configManagerProvider;
+ @Mixin
+ private FormatMixin format;
/** {@inheritDoc} */
@Override
@@ -60,15 +58,11 @@ public class NodeConfigShowReplCommand extends BaseCommand implements Runnable {
.map(this::nodeConfigShowCallInput)
.then(Flows.fromCall(call))
.verbose(verbose)
- .print(new JsonDecorator(isHighlightEnabled()))
+ .print(format.decorator())
.start();
}
private NodeConfigShowCallInput nodeConfigShowCallInput(String nodeUrl) {
return NodeConfigShowCallInput.builder().selector(selector).nodeUrl(nodeUrl).build();
}
-
- private boolean isHighlightEnabled() {
- return Boolean.parseBoolean(configManagerProvider.get().getCurrentProperty(CliConfigKeys.SYNTAX_HIGHLIGHTING.value()));
- }
}
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/highlighter/JsonAnsiHighlighter.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/highlighter/HoconAnsiHighlighter.java
similarity index 57%
copy from modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/highlighter/JsonAnsiHighlighter.java
copy to modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/highlighter/HoconAnsiHighlighter.java
index f22d834d88..51e351b110 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/highlighter/JsonAnsiHighlighter.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/highlighter/HoconAnsiHighlighter.java
@@ -19,45 +19,49 @@ package org.apache.ignite.internal.cli.commands.treesitter.highlighter;
import static org.apache.ignite.internal.cli.core.style.AnsiStringSupport.ansi;
+import java.util.EnumMap;
import java.util.Map;
+import org.apache.ignite.internal.cli.commands.treesitter.parser.HoconTokenType;
import org.apache.ignite.internal.cli.commands.treesitter.parser.Indexer;
-import org.apache.ignite.internal.cli.commands.treesitter.parser.JsonTokenType;
import org.apache.ignite.internal.cli.commands.treesitter.parser.Parser;
import org.apache.ignite.internal.cli.core.style.AnsiStringSupport;
import org.apache.ignite.internal.cli.core.style.AnsiStringSupport.Color;
/**
- * Class for highlighting JSON text with ANSI colors.
+ * Class for highlighting HOCON text with ANSI colors.
*/
-public class JsonAnsiHighlighter {
+public class HoconAnsiHighlighter {
- private static final Map<JsonTokenType, AnsiStringSupport.Color> colorMap = Map.of(
- JsonTokenType.QUOTE, Color.GREEN,
- JsonTokenType.STRING_CONTENT, Color.GREEN,
- JsonTokenType.BRACKET, Color.WHITE,
- JsonTokenType.COMMA, Color.WHITE,
- JsonTokenType.COLON, Color.WHITE,
- JsonTokenType.NUMBER, Color.BLUE,
- JsonTokenType.BOOL, Color.YELLOW_DARK,
- JsonTokenType.UNKNOWN, Color.GRAY,
- JsonTokenType.SPACE, Color.GRAY
- );
+ private static final Map<HoconTokenType, Color> COLOR_MAP = new EnumMap<>(HoconTokenType.class);
+
+ static {
+ COLOR_MAP.put(HoconTokenType.QUOTE, Color.GREEN);
+ COLOR_MAP.put(HoconTokenType.STRING, Color.GREEN);
+ COLOR_MAP.put(HoconTokenType.PATH, Color.WHITE);
+ COLOR_MAP.put(HoconTokenType.BRACKET, Color.GRAY);
+ COLOR_MAP.put(HoconTokenType.COMMA, Color.GRAY);
+ COLOR_MAP.put(HoconTokenType.COLON, Color.GRAY);
+ COLOR_MAP.put(HoconTokenType.EQUALS, Color.GRAY);
+ COLOR_MAP.put(HoconTokenType.NUMBER, Color.BLUE);
+ COLOR_MAP.put(HoconTokenType.BOOL, Color.YELLOW_DARK);
+ COLOR_MAP.put(HoconTokenType.UNKNOWN, Color.GRAY);
+ COLOR_MAP.put(HoconTokenType.SPACE, Color.GRAY);
+ }
/**
- * Highlights the input JSON text with ANSI colors.
+ * Highlights the input HOCON text with ANSI colors.
*
- * @param text The input JSON text.
- * @return The highlighted JSON text.
+ * @param text The input HOCON text.
+ * @return The highlighted HOCON text.
*/
public static String highlight(String text) {
- // TODO Use tree-sitter-hocon parser here IGNITE-23108
- var tree = Parser.parseJson(text);
- JsonTokenType[] tokens = Indexer.indexJson(text, tree);
+ var tree = Parser.parseHocon(text);
+ HoconTokenType[] tokens = Indexer.indexHocon(text, tree);
var sb = new StringBuilder();
for (int i = 0; i < text.length(); i++) {
- JsonTokenType token = tokens[i];
- Color color = colorMap.get(token);
+ HoconTokenType token = tokens[i];
+ Color color = COLOR_MAP.get(token);
sb.append(AnsiStringSupport.fg(color).mark(String.valueOf(text.charAt(i))));
}
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/highlighter/JsonAnsiHighlighter.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/highlighter/JsonAnsiHighlighter.java
index f22d834d88..d9ad073c09 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/highlighter/JsonAnsiHighlighter.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/highlighter/JsonAnsiHighlighter.java
@@ -50,7 +50,6 @@ public class JsonAnsiHighlighter {
* @return The highlighted JSON text.
*/
public static String highlight(String text) {
- // TODO Use tree-sitter-hocon parser here IGNITE-23108
var tree = Parser.parseJson(text);
JsonTokenType[] tokens = Indexer.indexJson(text, tree);
var sb = new StringBuilder();
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/parser/HoconTokenType.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/parser/HoconTokenType.java
new file mode 100644
index 0000000000..611f6ea960
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/parser/HoconTokenType.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cli.commands.treesitter.parser;
+
+import org.treesitter.TSNode;
+
+/**
+ * Enum representing the different types of tokens in a HOCON text.
+ */
+public enum HoconTokenType {
+ QUOTE,
+ STRING,
+ PATH,
+ NUMBER,
+ COLON,
+ EQUALS,
+ BRACKET,
+ COMMA,
+ UNKNOWN,
+ BOOL,
+ SPACE;
+
+ private static final String PATH_TYPE = "path";
+
+ static HoconTokenType fromNode(TSNode node) {
+ String nodeType = nodeType(node);
+ switch (nodeType) {
+ case "\"":
+ return QUOTE;
+ case PATH_TYPE:
+ return PATH;
+ case "string":
+ case "unquoted_string":
+ return STRING;
+ case "{":
+ case "}":
+ case "[":
+ case "]":
+ return BRACKET;
+ case ",":
+ return COMMA;
+ case ":":
+ return COLON;
+ case "=":
+ return EQUALS;
+ case "number":
+ return NUMBER;
+ case "true":
+ case "false":
+ return BOOL;
+ default:
+ return UNKNOWN;
+ }
+ }
+
+ private static String nodeType(TSNode node) {
+ TSNode parent = node;
+ // Both key and value nodes could have the same type - string - but we want to highlight the key ("path") differently.
+ // Proper fix would be to change the logic of tokenizing completely but this is enough for now.
+ while (!parent.isNull()) {
+ if (PATH_TYPE.equals(parent.getType())) {
+ return PATH_TYPE;
+ }
+ parent = parent.getParent();
+ }
+ return node.getType();
+ }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/parser/Indexer.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/parser/Indexer.java
index 0bd535bc97..6ca08cd83f 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/parser/Indexer.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/parser/Indexer.java
@@ -17,13 +17,14 @@
package org.apache.ignite.internal.cli.commands.treesitter.parser;
+import java.util.function.Function;
import org.treesitter.TSNode;
import org.treesitter.TSTree;
import org.treesitter.TSTreeCursor;
/**
* Builds an "index" for the provided text and abstract syntax tree.
- * The index is an array of tokens, where each token is a member of the SqlTokenType enum.
+ * The index is an array of tokens, where each token is a member of the corresponding enum.
* It is used to map each character in the input text to a token.
*/
public class Indexer {
@@ -36,21 +37,61 @@ public class Indexer {
* @return The index.
*/
public static SqlTokenType[] indexSql(String text, TSTree tree) {
- SqlTokenType[] tokens = new SqlTokenType[text.length()];
+ return index(text, tree, SqlTokenType[]::new, SqlTokenType.SPACE, SqlTokenType::fromNode);
+ }
+
+ /**
+ * Builds an index for the provided JSON text and abstract syntax tree.
+ *
+ * @param text The input JSON text.
+ * @param tree The abstract syntax tree.
+ * @return The index.
+ */
+ public static JsonTokenType[] indexJson(String text, TSTree tree) {
+ return index(text, tree, JsonTokenType[]::new, JsonTokenType.SPACE, JsonTokenType::fromNode);
+ }
+ /**
+ * Builds an index for the provided HOCON text and abstract syntax tree.
+ *
+ * @param text The input HOCON text.
+ * @param tree The abstract syntax tree.
+ * @return The index.
+ */
+ public static HoconTokenType[] indexHocon(String text, TSTree tree) {
+ return index(text, tree, HoconTokenType[]::new, HoconTokenType.SPACE, HoconTokenType::fromNode);
+ }
+
+ /**
+ * Builds an index for the provided text and abstract syntax tree.
+ *
+ * @param text The input text.
+ * @param tree The abstract syntax tree.
+ * @param tokensArrayFactory Function to create an array of tokens.
+ * @param spaceToken Token representing space.
+ * @param nodeToToken Function to extract a token from the node of the tree.
+ * @return The index.
+ */
+ private static <T> T[] index(
+ String text,
+ TSTree tree,
+ Function<Integer, T[]> tokensArrayFactory,
+ T spaceToken,
+ Function<TSNode, T> nodeToToken
+ ) {
+ T[] tokens = tokensArrayFactory.apply(text.length());
TSTreeCursor cursor = new TSTreeCursor(tree.getRootNode());
for (int i = 0; i < text.length(); i++) {
if (Character.isSpaceChar(text.charAt(i))) {
- tokens[i] = SqlTokenType.SPACE;
+ tokens[i] = spaceToken;
continue;
}
findTerminalNode(i, cursor);
TSNode node = cursor.currentNode();
- tokens[i] = SqlTokenType.fromNode(node);
+ tokens[i] = nodeToToken.apply(node);
cursor.reset(tree.getRootNode());
}
return tokens;
-
}
private static void findTerminalNode(int pos, TSTreeCursor cursor) {
@@ -71,28 +112,4 @@ public class Indexer {
private static boolean inside(int pos, TSNode node) {
return pos >= node.getStartByte() && pos < node.getEndByte();
}
-
- /**
- * Builds an index for the provided JSON text and abstract syntax tree.
- *
- * @param text The input JSON text.
- * @param tree The abstract syntax tree.
- * @return The index.
- */
- public static JsonTokenType[] indexJson(String text, TSTree tree) {
- JsonTokenType[] tokens = new JsonTokenType[text.length()];
-
- TSTreeCursor cursor = new TSTreeCursor(tree.getRootNode());
- for (int i = 0; i < text.length(); i++) {
- if (Character.isSpaceChar(text.charAt(i))) {
- tokens[i] = JsonTokenType.SPACE;
- continue;
- }
- findTerminalNode(i, cursor);
- TSNode node = cursor.currentNode();
- tokens[i] = JsonTokenType.fromNode(node);
- cursor.reset(tree.getRootNode());
- }
- return tokens;
- }
}
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/parser/Parser.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/parser/Parser.java
index d1ab8a821e..4fb6ef8856 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/parser/Parser.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/parser/Parser.java
@@ -17,8 +17,10 @@
package org.apache.ignite.internal.cli.commands.treesitter.parser;
+import org.treesitter.TSLanguage;
import org.treesitter.TSParser;
import org.treesitter.TSTree;
+import org.treesitter.TreeSitterHocon;
import org.treesitter.TreeSitterJson;
import org.treesitter.TreeSitterSql;
@@ -36,9 +38,7 @@ public final class Parser {
* @return The abstract syntax tree.
*/
public static TSTree parseSql(String text) {
- var parser = new TSParser();
- parser.setLanguage(new TreeSitterSql());
- return parser.parseString(null, text);
+ return parseLanguage(text, new TreeSitterSql());
}
/**
@@ -48,8 +48,29 @@ public final class Parser {
* @return The abstract syntax tree.
*/
public static TSTree parseJson(String text) {
- var parser = new TSParser();
- parser.setLanguage(new TreeSitterJson());
+ return parseLanguage(text, new TreeSitterJson());
+ }
+
+ /**
+ * Parses the input text as HOCON.
+ *
+ * @param text The input text.
+ * @return The abstract syntax tree.
+ */
+ public static TSTree parseHocon(String text) {
+ return parseLanguage(text, new TreeSitterHocon());
+ }
+
+ /**
+ * Parses the input text.
+ *
+ * @param text The input text.
+ * @param language The language to parse the text with.
+ * @return The abstract syntax tree.
+ */
+ private static TSTree parseLanguage(String text, TSLanguage language) {
+ TSParser parser = new TSParser();
+ parser.setLanguage(language);
return parser.parseString(null, text);
}
}
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/ReplExecutorImpl.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/ReplExecutorImpl.java
index c65742f917..d960a0c018 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/ReplExecutorImpl.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/ReplExecutorImpl.java
@@ -194,6 +194,7 @@ public class ReplExecutorImpl implements ReplExecutor {
CommandLineContextProvider.setCmd(cmd);
cmd.setExecutionExceptionHandler(new PicocliExecutionExceptionHandler(exceptionHandlers));
cmd.setTrimQuotes(true);
+ cmd.setCaseInsensitiveEnumValuesAllowed(true);
DynamicCompleterRegistry completerRegistry = factory.create(DynamicCompleterRegistry.class);
DynamicCompleterActivationPoint activationPoint = factory.create(DynamicCompleterActivationPoint.class);
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/JsonDecorator.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/HoconDecorator.java
similarity index 88%
copy from modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/JsonDecorator.java
copy to modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/HoconDecorator.java
index 57e7567000..e9fb50b965 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/JsonDecorator.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/HoconDecorator.java
@@ -22,18 +22,18 @@ import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigRenderOptions;
import org.apache.ignite.internal.cli.call.configuration.JsonString;
-import org.apache.ignite.internal.cli.commands.treesitter.highlighter.JsonAnsiHighlighter;
+import org.apache.ignite.internal.cli.commands.treesitter.highlighter.HoconAnsiHighlighter;
import org.apache.ignite.internal.cli.core.decorator.Decorator;
import org.apache.ignite.internal.cli.core.decorator.TerminalOutput;
/**
- * Pretty json decorator.
+ * Pretty hocon decorator.
*/
-public class JsonDecorator implements Decorator<JsonString, TerminalOutput> {
+public class HoconDecorator implements Decorator<JsonString, TerminalOutput> {
private final boolean highlight;
- public JsonDecorator(boolean highlight) {
+ public HoconDecorator(boolean highlight) {
this.highlight = highlight;
}
@@ -49,7 +49,7 @@ public class JsonDecorator implements Decorator<JsonString, TerminalOutput> {
text = text.substring(0, text.length() - 1);
}
- return highlight ? JsonAnsiHighlighter.highlight(text) : text;
+ return highlight ? HoconAnsiHighlighter.highlight(text) : text;
} catch (ConfigException.Parse e) {
return json.getValue(); // no-op
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/JsonDecorator.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/JsonDecorator.java
index 57e7567000..65c96f8766 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/JsonDecorator.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/JsonDecorator.java
@@ -17,10 +17,9 @@
package org.apache.ignite.internal.cli.decorators;
-import com.typesafe.config.Config;
-import com.typesafe.config.ConfigException;
-import com.typesafe.config.ConfigFactory;
-import com.typesafe.config.ConfigRenderOptions;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ignite.internal.cli.call.configuration.JsonString;
import org.apache.ignite.internal.cli.commands.treesitter.highlighter.JsonAnsiHighlighter;
import org.apache.ignite.internal.cli.core.decorator.Decorator;
@@ -40,18 +39,15 @@ public class JsonDecorator implements Decorator<JsonString, TerminalOutput> {
/** {@inheritDoc} */
@Override
public TerminalOutput decorate(JsonString json) {
+ ObjectMapper mapper = new ObjectMapper();
return () -> {
try {
- Config config = ConfigFactory.parseString(json.getValue());
- String text = config.root().render(ConfigRenderOptions.concise().setFormatted(true).setJson(false));
- if (text.endsWith("\n")) {
- // Config renders with the trailing line separator, but we also add a separator in the handleResult.
- text = text.substring(0, text.length() - 1);
- }
+ String text = mapper.writerWithDefaultPrettyPrinter()
+ .writeValueAsString(mapper.readValue(json.getValue(), JsonNode.class));
return highlight ? JsonAnsiHighlighter.highlight(text) : text;
- } catch (ConfigException.Parse e) {
+ } catch (JsonProcessingException e) {
return json.getValue(); // no-op
}
};
diff --git a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/treesitter/highlighter/JsonAnsiHighlighterTest.java b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/treesitter/highlighter/HighlighterTest.java
similarity index 69%
rename from modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/treesitter/highlighter/JsonAnsiHighlighterTest.java
rename to modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/treesitter/highlighter/HighlighterTest.java
index 3c7eea8f2b..729fa9b13a 100644
--- a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/treesitter/highlighter/JsonAnsiHighlighterTest.java
+++ b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/treesitter/highlighter/HighlighterTest.java
@@ -21,7 +21,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.params.provider.Arguments.of;
import java.util.stream.Stream;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@@ -29,10 +28,9 @@ import org.junit.jupiter.params.provider.MethodSource;
/**
* Tests for the highlighting errors in JSON text. The colors itself are not tested.
*/
-class JsonAnsiHighlighterTest {
- JsonAnsiHighlighter highlighter;
+class HighlighterTest {
- private static Stream<Arguments> textInput() {
+ private static Stream<Arguments> jsonTextInput() {
return Stream.of(
of("plain", "plain"),
of("{\"key\": \"val\"}", "{\"key\": \"val\"}"),
@@ -40,15 +38,25 @@ class JsonAnsiHighlighterTest {
);
}
- @BeforeEach
- void setUp() {
- highlighter = new JsonAnsiHighlighter();
+ @ParameterizedTest
+ @MethodSource("jsonTextInput")
+ void jsonHighlight(String input, String expected) {
+ String result = JsonAnsiHighlighter.highlight(input);
+ assertEquals(expected, result);
+ }
+
+ private static Stream<Arguments> hoconTextInput() {
+ return Stream.of(
+ of("plain", "plain"),
+ of("{\"key\": \"val\"}", "{\"key\": \"val\"}"),
+ of("key=val", "key=val")
+ );
}
@ParameterizedTest
- @MethodSource("textInput")
- void testHighlight(String input, String expected) {
- String result = highlighter.highlight(input);
+ @MethodSource("hoconTextInput")
+ void hoconHighlight(String input, String expected) {
+ String result = HoconAnsiHighlighter.highlight(input);
assertEquals(expected, result);
}
}
diff --git a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/treesitter/parser/HoconIndexerTest.java b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/treesitter/parser/HoconIndexerTest.java
new file mode 100644
index 0000000000..f9838c34cf
--- /dev/null
+++ b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/treesitter/parser/HoconIndexerTest.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cli.commands.treesitter.parser;
+
+import static org.apache.ignite.internal.cli.commands.treesitter.parser.HoconTokenType.BOOL;
+import static org.apache.ignite.internal.cli.commands.treesitter.parser.HoconTokenType.BRACKET;
+import static org.apache.ignite.internal.cli.commands.treesitter.parser.HoconTokenType.COLON;
+import static org.apache.ignite.internal.cli.commands.treesitter.parser.HoconTokenType.COMMA;
+import static org.apache.ignite.internal.cli.commands.treesitter.parser.HoconTokenType.EQUALS;
+import static org.apache.ignite.internal.cli.commands.treesitter.parser.HoconTokenType.NUMBER;
+import static org.apache.ignite.internal.cli.commands.treesitter.parser.HoconTokenType.PATH;
+import static org.apache.ignite.internal.cli.commands.treesitter.parser.HoconTokenType.QUOTE;
+import static org.apache.ignite.internal.cli.commands.treesitter.parser.HoconTokenType.SPACE;
+import static org.apache.ignite.internal.cli.commands.treesitter.parser.HoconTokenType.STRING;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Tests for the indexing logic for HOCON.
+ */
+public class HoconIndexerTest {
+ private static Stream<Arguments> hoconProvider() {
+ return Stream.of(
+ Arguments.of(
+ "{\"a\": 1, \"b\": \"t\", c=true}",
+ new HoconTokenType[]{
+ // {
+ BRACKET,
+ // "a"
+ PATH, PATH, PATH,
+ // :
+ COLON,
+ // 1
+ SPACE, NUMBER,
+ // ,
+ COMMA,
+ // "b"
+ SPACE, PATH, PATH, PATH,
+ // :
+ COLON,
+ // "t"
+ SPACE, QUOTE, STRING, QUOTE,
+ // ,
+ COMMA,
+ // c
+ SPACE, PATH,
+ // =
+ EQUALS,
+ // true
+ BOOL, BOOL, BOOL, BOOL,
+ // }
+ BRACKET
+ }
+ )
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("hoconProvider")
+ void indexHocon(String text, HoconTokenType[] expectedTokens) {
+ var tree = Parser.parseHocon(text);
+ HoconTokenType[] tokens = Indexer.indexHocon(text, tree);
+ assertArrayEquals(expectedTokens, tokens);
+ }
+}