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}