001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.terminal.report; 018 019import java.nio.file.*; 020import java.util.*; 021import java.util.regex.*; 022 023/** 024 * Text helpers shared by the report readers. 025 */ 026public final class ReportText { 027 028 private static final Pattern ANSI = Pattern.compile("\u001b\\[[0-9;]*[A-Za-z]"); 029 030 private ReportText() {} 031 032 /** 033 * Removes ANSI escape sequences (test runners colour their messages). 034 * 035 * @param s The text. May be <jk>null</jk>. 036 * @return The text without escape sequences, or an empty string for <jk>null</jk>. 037 */ 038 public static String stripAnsi(String s) { 039 return s == null ? "" : ANSI.matcher(s).replaceAll(""); 040 } 041 042 /** 043 * Truncates a string to at most {@code max} chars, ending in an ellipsis when it was cut. 044 * 045 * @param s The text. May be <jk>null</jk>. 046 * @param max The maximum length, at least 1. 047 * @return The clipped text, or an empty string for <jk>null</jk>. 048 */ 049 public static String clip(String s, int max) { 050 if (s == null) 051 return ""; 052 return s.length() <= max ? s : s.substring(0, Math.max(0, max - 1)) + "…"; 053 } 054 055 /** 056 * Returns the file name of a path, never the directories (warnings leave the machine). 057 * 058 * @param path The path. 059 * @return The last segment, or an empty string. 060 */ 061 public static String fileName(Path path) { 062 var n = path == null ? null : path.getFileName(); 063 return n == null ? "" : n.toString(); 064 } 065 066 /** 067 * Shortens a list of test file paths for display. 068 * 069 * <p> 070 * Backslashes become slashes, then the longest common <i>directory</i> prefix of all paths is removed, so 071 * {@code /ci/work/repo/src/a.test.js} and {@code /ci/work/repo/src/b/c.test.js} become {@code a.test.js} and 072 * {@code b/c.test.js}. A list with a single path keeps its last two segments ({@code src/a.test.js}). 073 * 074 * @param paths The paths. 075 * @return The display paths, in the same order. 076 */ 077 public static List<String> stripCommonDir(List<String> paths) { 078 var segs = paths.stream().map(p -> p.replace('\\', '/').split("/", -1)).toList(); 079 var out = new ArrayList<String>(paths.size()); 080 if (segs.isEmpty()) 081 return out; 082 if (segs.size() == 1) { 083 var s = segs.get(0); 084 out.add(String.join("/", Arrays.copyOfRange(s, Math.max(0, s.length - 2), s.length))); 085 return out; 086 } 087 var common = Integer.MAX_VALUE; 088 for (var s : segs) 089 common = Math.min(common, s.length - 1); 090 var first = segs.get(0); 091 var keep = 0; 092 while (keep < common && allEqual(segs, first[keep], keep)) 093 keep++; 094 var drop = keep; 095 out.addAll(segs.stream().map(s -> String.join("/", Arrays.copyOfRange(s, drop, s.length))).toList()); 096 return out; 097 } 098 099 private static boolean allEqual(List<String[]> segs, String value, int index) { 100 for (var s : segs) 101 if (! s[index].equals(value)) 102 return false; 103 return true; 104 } 105}