You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2023/02/13 17:33:12 UTC

[camel] 02/03: CAMEL-19033: camel-jbang - get trace in color output of json so its easier to read

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 <cl...@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();
+        }
+    }
+
+}