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
commit 34a1bf1ffc08d005ff095a1883da467b4aa2e786 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Mon Feb 13 16:29:23 2023 +0100 CAMEL-19033: camel-jbang - get trace in color output of json so its easier to read --- .../dsl/jbang/core/commands/process/ListTrace.java | 38 ++++++- .../camel/dsl/jbang/core/common/JSonHelper.java | 54 ++++++++++ .../java/org/apache/camel/util/json/Jsoner.java | 111 +++++++++++++++++++++ .../java/org/apache/camel/util/json/Yytoken.java | 8 +- .../camel/util/json/JSonerColorPrintTest.java | 83 +++++++++++++++ 5 files changed, 287 insertions(+), 7 deletions(-) diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListTrace.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListTrace.java index b9aaf09c32a..a77ea0caf96 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListTrace.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListTrace.java @@ -27,6 +27,7 @@ import com.github.freva.asciitable.Column; import com.github.freva.asciitable.HorizontalAlign; import com.github.freva.asciitable.OverflowBehaviour; import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; +import org.apache.camel.dsl.jbang.core.common.JSonHelper; import org.apache.camel.dsl.jbang.core.common.ProcessHelper; import org.apache.camel.util.IOHelper; import org.apache.camel.util.TimeUtils; @@ -92,7 +93,7 @@ public class ListTrace extends ProcessWatchCommand { rows.sort(this::sortRow); if (!rows.isEmpty()) { - System.out.println(AsciiTable.getTable(AsciiTable.NO_BORDERS, rows, Arrays.asList( + String data = AsciiTable.getTable(AsciiTable.NO_BORDERS, rows, Arrays.asList( new Column().header("PID").headerAlign(HorizontalAlign.CENTER).with(r -> r.pid), new Column().header("NAME").dataAlign(HorizontalAlign.LEFT).maxWidth(30, OverflowBehaviour.ELLIPSIS_RIGHT) .with(r -> r.name), @@ -103,8 +104,23 @@ public class ListTrace extends ProcessWatchCommand { new Column().header("ID").dataAlign(HorizontalAlign.LEFT).maxWidth(25, OverflowBehaviour.ELLIPSIS_RIGHT) .with(this::getId), new Column().header("AGE").dataAlign(HorizontalAlign.RIGHT).with(this::getTimestamp), - new Column().header("MESSAGE").dataAlign(HorizontalAlign.LEFT).maxWidth(110, OverflowBehaviour.NEWLINE) - .with(this::getMessage)))); + new Column().header("ELAPSED").dataAlign(HorizontalAlign.RIGHT).with(this::getElapsed), + new Column().header("FAILED").dataAlign(HorizontalAlign.RIGHT).with(this::getFailed))); + String[] arr = data.split(System.lineSeparator()); + // print header + System.out.println(arr[0]); + // mix column and message (master/detail) mode + for (int i = 0; i < rows.size(); i++) { + String s = arr[i + 1]; + System.out.println(s); + String json = getMessage(rows.get(i)); + // pad with 8 spaces to indent json data + String[] lines = json.split(System.lineSeparator()); + for (String line : lines) { + System.out.print(" "); + System.out.println(line); + } + } } return 0; @@ -173,6 +189,20 @@ public class ListTrace extends ProcessWatchCommand { return ""; } + private String getElapsed(Row r) { + if (r.elapsed > 0) { + return TimeUtils.printDuration(r.elapsed, true); + } + return ""; + } + + private String getFailed(Row r) { + if (r.failed) { + return "1"; + } + return "0"; + } + private String getUid(Row r) { return "" + r.uid; } @@ -194,7 +224,7 @@ public class ListTrace extends ProcessWatchCommand { private String getMessage(Row r) { String s = r.message.toJson(); if (pretty) { - s = Jsoner.prettyPrint(s, 2); + s = JSonHelper.colorPrint(s, 2); } return s; } diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/JSonHelper.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/JSonHelper.java new file mode 100644 index 00000000000..7d1b06f2d35 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/JSonHelper.java @@ -0,0 +1,54 @@ +/* + * 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.camel.dsl.jbang.core.common; + +import org.apache.camel.util.json.Jsoner; +import org.apache.camel.util.json.Yytoken; +import org.fusesource.jansi.Ansi; + +public final class JSonHelper { + + private JSonHelper() { + } + + /** + * Prints the JSon in ANSi color (similar to jq) + */ + public static String colorPrint(String json, int spaces) { + return Jsoner.colorPrint(json, spaces, new Jsoner.ColorPrintElement() { + Yytoken.Types prev; + + @Override + public String color(Yytoken.Types type, Object value) { + String s = value.toString(); + switch (type) { + case COLON, COMMA, LEFT_SQUARE, RIGHT_SQUARE, LEFT_BRACE, RIGHT_BRACE -> + s = Ansi.ansi().fg(Ansi.Color.WHITE).bold().a(s).reset().toString(); + case VALUE -> { + if (Yytoken.Types.COLON == prev) { + s = Ansi.ansi().fg(Ansi.Color.GREEN).a(s).reset().toString(); + } else { + s = Ansi.ansi().fgBright(Ansi.Color.BLUE).a(s).reset().toString(); + } + } + } + prev = type; + return s; + } + }); + } +} diff --git a/tooling/camel-util-json/src/main/java/org/apache/camel/util/json/Jsoner.java b/tooling/camel-util-json/src/main/java/org/apache/camel/util/json/Jsoner.java index a2624a74478..573fe7eb484 100644 --- a/tooling/camel-util-json/src/main/java/org/apache/camel/util/json/Jsoner.java +++ b/tooling/camel-util-json/src/main/java/org/apache/camel/util/json/Jsoner.java @@ -732,6 +732,117 @@ public final class Jsoner { return returnable.toString(); } + @FunctionalInterface + public interface ColorPrintElement { + String color(Yytoken.Types type, Object value); + } + + public static String colorPrint(final String printable, final ColorPrintElement color) { + return Jsoner.colorPrint(printable, "\t", Integer.MAX_VALUE, color); + } + + public static String colorPrint(final String printable, final int spaces, final ColorPrintElement color) { + if (spaces > 10 || spaces < 2) { + throw new IllegalArgumentException("Indentation with spaces must be between 2 and 10."); + } + final StringBuilder indentation = new StringBuilder(""); + for (int i = 0; i < spaces; i++) { + indentation.append(" "); + } + return Jsoner.colorPrint(printable, indentation.toString(), Integer.MAX_VALUE, color); + } + + public static String colorPrint( + final String printable, final String indentation, final int depth, ColorPrintElement color) { + final Yylex lexer = new Yylex(new StringReader(printable)); + Yytoken lexed; + final StringBuilder returnable = new StringBuilder(); + int level = 0; + try { + do { + lexed = Jsoner.lexNextToken(lexer); + switch (lexed.getType()) { + case COLON: + returnable.append(color.color(Yytoken.Types.COLON, ":")).append(" "); + break; + case COMMA: + returnable.append(color.color(Yytoken.Types.COMMA, lexed.getValue())); + if (level <= depth) { + returnable.append("\n"); + for (int i = 0; i < level; i++) { + returnable.append(indentation); + } + } else { + returnable.append(" "); + } + break; + case END: + returnable.append("\n"); + break; + case LEFT_BRACE: + returnable.append(color.color(Yytoken.Types.LEFT_BRACE, lexed.getValue())); + if (++level <= depth) { + returnable.append("\n"); + for (int i = 0; i < level; i++) { + returnable.append(indentation); + } + } else { + returnable.append(" "); + } + break; + case LEFT_SQUARE: + returnable.append(color.color(Yytoken.Types.LEFT_SQUARE, lexed.getValue())); + if (++level <= depth) { + returnable.append("\n"); + for (int i = 0; i < level; i++) { + returnable.append(indentation); + } + } else { + returnable.append(" "); + } + break; + case RIGHT_BRACE: + if (level-- <= depth) { + returnable.append("\n"); + for (int i = 0; i < level; i++) { + returnable.append(indentation); + } + } else { + returnable.append(" "); + } + returnable.append(color.color(Yytoken.Types.RIGHT_BRACE, lexed.getValue())); + break; + case RIGHT_SQUARE: + if (level-- <= depth) { + returnable.append("\n"); + for (int i = 0; i < level; i++) { + returnable.append(indentation); + } + } else { + returnable.append(" "); + } + returnable.append(color.color(Yytoken.Types.RIGHT_SQUARE, lexed.getValue())); + break; + default: + if (lexed.getValue() instanceof String) { + String s = "\"" + Jsoner.escape((String) lexed.getValue()) + "\""; + returnable.append(color.color(Yytoken.Types.VALUE, s)); + } else { + returnable.append(color.color(Yytoken.Types.VALUE, lexed.getValue())); + } + break; + } + } while (!lexed.getType().equals(Yytoken.Types.END)); + } catch (final DeserializationException caught) { + /* This is according to the method's contract. */ + return null; + } catch (final IOException caught) { + /* See StringReader. */ + return null; + } + return returnable.toString(); + } + /** * A convenience method that assumes a StringWriter. * diff --git a/tooling/camel-util-json/src/main/java/org/apache/camel/util/json/Yytoken.java b/tooling/camel-util-json/src/main/java/org/apache/camel/util/json/Yytoken.java index 5d1a583969f..5ef989f7233 100644 --- a/tooling/camel-util-json/src/main/java/org/apache/camel/util/json/Yytoken.java +++ b/tooling/camel-util-json/src/main/java/org/apache/camel/util/json/Yytoken.java @@ -21,9 +21,9 @@ package org.apache.camel.util.json; * * @since 2.0.0 */ -class Yytoken { +public class Yytoken { /** Represents the different kinds of tokens. */ - enum Types { + public enum Types { /** Tokens of this type will always have a value of ":" */ COLON, /** Tokens of this type will always have a value of "," */ @@ -41,7 +41,9 @@ class Yytoken { /** Tokens of this type will always have a value of "}" */ RIGHT_BRACE, /** Tokens of this type will always have a value of "]" */ - RIGHT_SQUARE; + RIGHT_SQUARE, + /** Represent the value (not a parsing token but used during color print) */ + VALUE; } private final Types type; diff --git a/tooling/camel-util-json/src/test/java/org/apache/camel/util/json/JSonerColorPrintTest.java b/tooling/camel-util-json/src/test/java/org/apache/camel/util/json/JSonerColorPrintTest.java new file mode 100644 index 00000000000..436835072f1 --- /dev/null +++ b/tooling/camel-util-json/src/test/java/org/apache/camel/util/json/JSonerColorPrintTest.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.camel.util.json; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class JSonerColorPrintTest { + + @Test + public void testColor() throws Exception { + InputStream is = new FileInputStream("src/test/resources/bean.json"); + String json = loadText(is); + + String color = Jsoner.colorPrint(json, new Jsoner.ColorPrintElement() { + Yytoken.Types prev; + + @Override + public String color(Yytoken.Types type, Object value) { + String answer; + if (Yytoken.Types.VALUE == type) { + if (Yytoken.Types.COLON == prev) { + // value + answer = "GREEN" + value.toString(); + } else { + // value + answer = "BLUE" + value.toString(); + } + } else { + answer = value.toString(); + } + prev = type; + return answer; + } + }); + + Assertions.assertTrue(color.contains("BLUE\"title\": GREEN\"Bean\"")); + } + + public static String loadText(InputStream in) throws IOException { + StringBuilder builder = new StringBuilder(); + InputStreamReader isr = new InputStreamReader(in); + + try { + BufferedReader reader = new BufferedReader(isr); + + while (true) { + String line = reader.readLine(); + if (line == null) { + line = builder.toString(); + return line; + } + + builder.append(line); + builder.append("\n"); + } + } finally { + isr.close(); + in.close(); + } + } + +}