You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@uniffle.apache.org by zu...@apache.org on 2023/06/29 09:19:32 UTC
[incubator-uniffle] branch master updated: [#978][Improvement] Provides a tool class to format CLI output content (#979)
This is an automated email from the ASF dual-hosted git repository.
zuston pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-uniffle.git
The following commit(s) were added to refs/heads/master by this push:
new f622aca8 [#978][Improvement] Provides a tool class to format CLI output content (#979)
f622aca8 is described below
commit f622aca8a453000874d6885654ff3029d1e38fc8
Author: yl09099 <33...@users.noreply.github.com>
AuthorDate: Thu Jun 29 17:19:26 2023 +0800
[#978][Improvement] Provides a tool class to format CLI output content (#979)
### What changes were proposed in this pull request?
Provides a tool class to format CLI output content.
### Why are the changes needed?
This tool is to make CLI output layout much better, especially for the apps/servers display
### Does this PR introduce _any_ user-facing change?
The CLI command display is more beautiful.Similar to:
![image](https://github.com/apache/incubator-uniffle/assets/33595968/2829da56-fdf6-4934-95dc-e59ecef2e128)
### How was this patch tested?
Added UT.
---
.../org/apache/uniffle/cli/CLIContentUtils.java | 278 +++++++++++++++++++++
.../apache/uniffle/cli/CLIContentUtilsTest.java | 111 ++++++++
cli/src/test/resources/CLIContentResult | 32 +++
3 files changed, 421 insertions(+)
diff --git a/cli/src/main/java/org/apache/uniffle/cli/CLIContentUtils.java b/cli/src/main/java/org/apache/uniffle/cli/CLIContentUtils.java
new file mode 100644
index 00000000..bf8cfcb5
--- /dev/null
+++ b/cli/src/main/java/org/apache/uniffle/cli/CLIContentUtils.java
@@ -0,0 +1,278 @@
+/*
+ * 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.uniffle.cli;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The main core class that generates the ASCII TABLE.
+ */
+public final class CLIContentUtils {
+ /** Table title. */
+ private String title;
+ /** Last processed row type. */
+ private TableRowType lastTableRowType;
+ /** StringBuilder object used to concatenate strings. */
+ private StringBuilder join;
+ /** An ordered Map that holds each row of data. */
+ private List<TableRow> tableRows;
+ /** Maps the maximum length of each column. */
+ private Map<Integer, Integer> maxColMap;
+
+ /**
+ * Contains the title constructor.
+ * @param title titleName
+ */
+ public CLIContentUtils(String title) {
+ this.init();
+ this.title = title;
+ }
+
+ /**
+ * Initialize the data.
+ */
+ private void init() {
+ this.join = new StringBuilder();
+ this.tableRows = new ArrayList<>();
+ this.maxColMap = new HashMap<>();
+ }
+
+ /**
+ * Adds elements from the collection to the header data in the table.
+ * @param headers Header data
+ * @return FormattingCLIUtils object
+ */
+ public CLIContentUtils addHeaders(List<?> headers) {
+ return this.appendRows(TableRowType.HEADER, headers.toArray());
+ }
+
+ /**
+ * Adds a row of normal data to the table.
+ * @param objects Common row data
+ * @return FormattingCLIUtils object
+ */
+ public CLIContentUtils addLine(Object... objects) {
+ return this.appendRows(TableRowType.LINE, objects);
+ }
+
+ /**
+ * Adds the middle row of data to the table.
+ * @param tableRowType TableRowType
+ * @param objects Table row data
+ * @return FormattingCLIUtils object
+ */
+ private CLIContentUtils appendRows(TableRowType tableRowType, Object... objects) {
+ if (objects != null && objects.length > 0) {
+ int len = objects.length;
+ if (this.maxColMap.size() > len) {
+ throw new IllegalArgumentException("The number of columns that inserted a row "
+ + "of data into the table is different from the number of previous columns, check!");
+ }
+ List<String> lines = new ArrayList<>();
+ for (int i = 0; i < len; i++) {
+ Object o = objects[i];
+ String value = o == null ? "null" : o.toString();
+ lines.add(value);
+ Integer maxColSize = this.maxColMap.get(i);
+ if (maxColSize == null) {
+ this.maxColMap.put(i, value.length());
+ continue;
+ }
+ if (value.length() > maxColSize) {
+ this.maxColMap.put(i, value.length());
+ }
+ }
+ this.tableRows.add(new TableRow(tableRowType, lines));
+ }
+ return this;
+ }
+
+ /**
+ * Builds the string for the row of the table title.
+ */
+ private void buildTitle() {
+ if (this.title != null) {
+ int maxTitleSize = 0;
+ for (Integer maxColSize : this.maxColMap.values()) {
+ maxTitleSize += maxColSize;
+ }
+ maxTitleSize += 3 * (this.maxColMap.size() - 1);
+ if (this.title.length() > maxTitleSize) {
+ this.title = this.title.substring(0, maxTitleSize);
+ }
+ this.join.append("+");
+ for (int i = 0; i < maxTitleSize + 2; i++) {
+ this.join.append("-");
+ }
+ this.join.append("+\n")
+ .append("|")
+ .append(StrUtils.center(this.title, maxTitleSize + 2, ' '))
+ .append("|\n");
+ this.lastTableRowType = TableRowType.TITLE;
+ }
+ }
+
+ /**
+ * Build the table, first build the title, and then walk through each row of data to build.
+ */
+ private void buildTable() {
+ this.buildTitle();
+ for (int i = 0, len = this.tableRows.size(); i < len; i++) {
+ List<String> data = this.tableRows.get(i).data;
+ switch (this.tableRows.get(i).tableRowType) {
+ case HEADER:
+ if (this.lastTableRowType != TableRowType.HEADER) {
+ this.buildRowBorder(data);
+ }
+ this.buildRowLine(data);
+ this.buildRowBorder(data);
+ break;
+ case LINE:
+ this.buildRowLine(data);
+ if (i == len - 1) {
+ this.buildRowBorder(data);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * Method to build a border row.
+ * @param data dataLine
+ */
+ private void buildRowBorder(List<String> data) {
+ this.join.append("+");
+ for (int i = 0, len = data.size(); i < len; i++) {
+ for (int j = 0; j < this.maxColMap.get(i) + 2; j++) {
+ this.join.append("-");
+ }
+ this.join.append("+");
+ }
+ this.join.append("\n");
+ }
+
+ /**
+ * A way to build rows of data.
+ * @param data dataLine
+ */
+ private void buildRowLine(List<String> data) {
+ this.join.append("|");
+ for (int i = 0, len = data.size(); i < len; i++) {
+ this.join.append(StrUtils.center(data.get(i), this.maxColMap.get(i) + 2, ' '))
+ .append("|");
+ }
+ this.join.append("\n");
+ }
+
+ /**
+ * Rendering is born as a result.
+ * @return ASCII string of Table
+ */
+ public String render() {
+ this.buildTable();
+ return this.join.toString();
+ }
+
+ /**
+ * The type of each table row and the entity class of the data.
+ */
+ private static class TableRow {
+ private TableRowType tableRowType;
+ private List<String> data;
+
+ TableRow(TableRowType tableRowType, List<String> data) {
+ this.tableRowType = tableRowType;
+ this.data = data;
+ }
+ }
+
+ /**
+ * An enumeration class that distinguishes between table headers and normal table data.
+ */
+ private enum TableRowType {
+ TITLE, HEADER, LINE
+ }
+
+ /**
+ * String utility class.
+ */
+ private static final class StrUtils {
+ /**
+ * Puts a string in the middle of a given size.
+ * @param str Character string
+ * @param size Total size
+ * @param padChar Fill character
+ * @return String result
+ */
+ private static String center(String str, int size, char padChar) {
+ if (str != null && size > 0) {
+ int strLen = str.length();
+ int pads = size - strLen;
+ if (pads > 0) {
+ str = leftPad(str, strLen + pads / 2, padChar);
+ str = rightPad(str, size, padChar);
+ }
+ }
+ return str;
+ }
+
+ /**
+ * Left-fill the given string and size.
+ * @param str String
+ * @param size totalSize
+ * @param padChar Fill character
+ * @return String result
+ */
+ private static String leftPad(final String str, int size, char padChar) {
+ int pads = size - str.length();
+ return pads <= 0 ? str : repeat(padChar, pads).concat(str);
+ }
+
+ /**
+ * Right-fill the given string and size.
+ * @param str String
+ * @param size totalSize
+ * @param padChar Fill character
+ * @return String result
+ */
+ private static String rightPad(final String str, int size, char padChar) {
+ int pads = size - str.length();
+ return pads <= 0 ? str : str.concat(repeat(padChar, pads));
+ }
+
+ /**
+ * Re-fill characters as strings.
+ * @param ch String
+ * @param repeat Number of repeats
+ * @return String
+ */
+ private static String repeat(char ch, int repeat) {
+ char[] buf = new char[repeat];
+ for (int i = repeat - 1; i >= 0; i--) {
+ buf[i] = ch;
+ }
+ return new String(buf);
+ }
+ }
+}
diff --git a/cli/src/test/java/org/apache/uniffle/cli/CLIContentUtilsTest.java b/cli/src/test/java/org/apache/uniffle/cli/CLIContentUtilsTest.java
new file mode 100644
index 00000000..12275154
--- /dev/null
+++ b/cli/src/test/java/org/apache/uniffle/cli/CLIContentUtilsTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.uniffle.cli;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.text.DecimalFormat;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class CLIContentUtilsTest {
+
+ @Test
+ public void testTableFormat() throws IOException, URISyntaxException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintWriter writer = new PrintWriter(baos);
+ String titleString = " 5 ShuffleServers were found";
+ List<String> headerStrings = Arrays.asList("HostName", "IP", "Port", "UsedMem",
+ "PreAllocatedMem", "AvaliableMem", "TotalMem", "Status");
+ CLIContentUtils formattingCLIUtils = new CLIContentUtils(titleString)
+ .addHeaders(headerStrings);
+ DecimalFormat df = new DecimalFormat("#.00");
+ formattingCLIUtils.addLine("uniffledata-hostname01", "10.93.23.11",
+ "9909", df.format(0.59 * 100) + "%",
+ df.format(0.74 * 100) + "%",
+ df.format(0.34 * 100) + "%",
+ df.format(105) + "G",
+ "ACTIVE");
+ formattingCLIUtils.addLine("uniffledata-hostname02", "10.93.23.12",
+ "9909", df.format(0.54 * 100) + "%",
+ df.format(0.78 * 100) + "%",
+ df.format(0.55 * 100) + "%",
+ df.format(105) + "G",
+ "ACTIVE");
+ formattingCLIUtils.addLine("uniffledata-hostname03", "10.93.23.13",
+ "9909", df.format(0.55 * 100) + "%",
+ df.format(0.56 * 100) + "%",
+ df.format(0.79 * 100) + "%",
+ df.format(105) + "G",
+ "ACTIVE");
+ formattingCLIUtils.addLine("uniffledata-hostname04", "10.93.23.14",
+ "9909", df.format(0.34 * 100) + "%",
+ df.format(0.84 * 100) + "%",
+ df.format(0.64 * 100) + "%",
+ df.format(105) + "G",
+ "ACTIVE");
+ formattingCLIUtils.addLine("uniffledata-hostname05", "10.93.23.15",
+ "9909", df.format(0.34 * 100) + "%",
+ df.format(0.89 * 100) + "%",
+ df.format(0.16 * 100) + "%",
+ df.format(105) + "G",
+ "ACTIVE");
+ formattingCLIUtils.addLine("uniffledata-hostname06", "10.93.23.16",
+ "9909", df.format(0.34 * 100) + "%",
+ df.format(0.45 * 100) + "%",
+ df.format(0.67 * 100) + "%",
+ df.format(105) + "G",
+ "ACTIVE");
+ formattingCLIUtils.addLine("uniffledata-hostname07", "10.93.23.17",
+ "9909", df.format(0.34 * 100) + "%",
+ df.format(0.15 * 100) + "%",
+ df.format(0.98 * 100) + "%",
+ df.format(105) + "G",
+ "ACTIVE");
+ formattingCLIUtils.addLine("uniffledata-hostname08", "10.93.23.18",
+ "9909", df.format(0.34 * 100) + "%",
+ df.format(0.77 * 100) + "%",
+ df.format(0.67 * 100) + "%",
+ df.format(105) + "G",
+ "ACTIVE");
+ formattingCLIUtils.addLine("rssdata-hostname09", "10.93.23.19",
+ "9909", df.format(0.14 * 100) + "%",
+ df.format(0.44 * 100) + "%",
+ df.format(0.68 * 100) + "%",
+ df.format(100) + "G",
+ "LOST");
+ StringBuilder resultStrBuilder = new StringBuilder();
+ List<String> lines = Files.readAllLines(Paths
+ .get(this.getClass().getResource("/CLIContentResult").toURI()));
+ for (String line : lines) {
+ if (line != null && line.length() != 0 && !line.startsWith("#")) {
+ resultStrBuilder.append(line + "\n");
+ }
+ }
+ String expectStr = resultStrBuilder.toString();
+ assertEquals(expectStr, formattingCLIUtils.render());
+ }
+}
diff --git a/cli/src/test/resources/CLIContentResult b/cli/src/test/resources/CLIContentResult
new file mode 100644
index 00000000..bf2d44d2
--- /dev/null
+++ b/cli/src/test/resources/CLIContentResult
@@ -0,0 +1,32 @@
+#
+# 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.
+#
+
++------------------------------------------------------------------------------------------------------------+
+| 5 ShuffleServers were found |
++------------------------+-------------+------+---------+-----------------+--------------+----------+--------+
+| HostName | IP | Port | UsedMem | PreAllocatedMem | AvaliableMem | TotalMem | Status |
++------------------------+-------------+------+---------+-----------------+--------------+----------+--------+
+| uniffledata-hostname01 | 10.93.23.11 | 9909 | 59.00% | 74.00% | 34.00% | 105.00G | ACTIVE |
+| uniffledata-hostname02 | 10.93.23.12 | 9909 | 54.00% | 78.00% | 55.00% | 105.00G | ACTIVE |
+| uniffledata-hostname03 | 10.93.23.13 | 9909 | 55.00% | 56.00% | 79.00% | 105.00G | ACTIVE |
+| uniffledata-hostname04 | 10.93.23.14 | 9909 | 34.00% | 84.00% | 64.00% | 105.00G | ACTIVE |
+| uniffledata-hostname05 | 10.93.23.15 | 9909 | 34.00% | 89.00% | 16.00% | 105.00G | ACTIVE |
+| uniffledata-hostname06 | 10.93.23.16 | 9909 | 34.00% | 45.00% | 67.00% | 105.00G | ACTIVE |
+| uniffledata-hostname07 | 10.93.23.17 | 9909 | 34.00% | 15.00% | 98.00% | 105.00G | ACTIVE |
+| uniffledata-hostname08 | 10.93.23.18 | 9909 | 34.00% | 77.00% | 67.00% | 105.00G | ACTIVE |
+| rssdata-hostname09 | 10.93.23.19 | 9909 | 14.00% | 44.00% | 68.00% | 100.00G | LOST |
++------------------------+-------------+------+---------+-----------------+--------------+----------+--------+