Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | // Copyright 2015 The Bazel Authors. All rights reserved. |
Francois-Rene Rideau | c3ad541 | 2015-06-05 22:13:21 +0000 | [diff] [blame] | 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | package com.google.devtools.build.lib.syntax; |
| 15 | |
Klaus Aehlig | 26c86f8 | 2018-05-29 06:57:03 -0700 | [diff] [blame] | 16 | import com.google.common.base.Strings; |
Florian Weikert | 2591e19 | 2015-10-05 14:24:51 +0000 | [diff] [blame] | 17 | import com.google.common.collect.ImmutableSet; |
| 18 | import com.google.common.collect.Iterables; |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 19 | import com.google.devtools.build.lib.events.Location; |
tomlu | 67c84b1 | 2017-11-06 19:49:16 +0100 | [diff] [blame] | 20 | import com.google.devtools.build.lib.skylarkinterface.SkylarkPrintable; |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 21 | import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter; |
John Field | 585d1a0 | 2015-12-16 16:03:52 +0000 | [diff] [blame] | 22 | import com.google.devtools.build.lib.skylarkinterface.SkylarkValue; |
Francois-Rene Rideau | 4e99410 | 2015-09-17 22:41:28 +0000 | [diff] [blame] | 23 | import com.google.devtools.build.lib.syntax.SkylarkList.Tuple; |
Francois-Rene Rideau | c3ad541 | 2015-06-05 22:13:21 +0000 | [diff] [blame] | 24 | import java.io.IOException; |
brandjon | f107a53 | 2017-08-16 22:46:02 +0200 | [diff] [blame] | 25 | import java.util.Arrays; |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 26 | import java.util.Formattable; |
| 27 | import java.util.Formatter; |
brandjon | a9cafcb | 2018-04-04 12:24:33 -0700 | [diff] [blame] | 28 | import java.util.IllegalFormatException; |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 29 | import java.util.List; |
| 30 | import java.util.Map; |
| 31 | import java.util.MissingFormatWidthException; |
brandjon | a9cafcb | 2018-04-04 12:24:33 -0700 | [diff] [blame] | 32 | import java.util.UnknownFormatConversionException; |
Florian Weikert | 2591e19 | 2015-10-05 14:24:51 +0000 | [diff] [blame] | 33 | import java.util.regex.Matcher; |
| 34 | import java.util.regex.Pattern; |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 35 | import javax.annotation.Nullable; |
Francois-Rene Rideau | c3ad541 | 2015-06-05 22:13:21 +0000 | [diff] [blame] | 36 | |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 37 | /** (Pretty) Printing of Skylark values */ |
| 38 | public class Printer { |
Francois-Rene Rideau | c3ad541 | 2015-06-05 22:13:21 +0000 | [diff] [blame] | 39 | |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 40 | public static final char SKYLARK_QUOTATION_MARK = '"'; |
Francois-Rene Rideau | 304e195 | 2015-08-25 13:30:10 +0000 | [diff] [blame] | 41 | |
Florian Weikert | 2591e19 | 2015-10-05 14:24:51 +0000 | [diff] [blame] | 42 | /* |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 43 | * Suggested maximum number of list elements that should be printed via printAbbreviatedList(). |
Florian Weikert | 2591e19 | 2015-10-05 14:24:51 +0000 | [diff] [blame] | 44 | * By default, this setting is not considered and no limitation takes place. |
| 45 | */ |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 46 | public static final int SUGGESTED_CRITICAL_LIST_ELEMENTS_COUNT = 4; |
Florian Weikert | 2591e19 | 2015-10-05 14:24:51 +0000 | [diff] [blame] | 47 | |
| 48 | /* |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 49 | * Suggested limit for printAbbreviatedList() to shorten the values of list elements when |
| 50 | * their combined string length reaches this value. |
Florian Weikert | 2591e19 | 2015-10-05 14:24:51 +0000 | [diff] [blame] | 51 | * By default, this setting is not considered and no limitation takes place. |
| 52 | */ |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 53 | public static final int SUGGESTED_CRITICAL_LIST_ELEMENTS_STRING_LENGTH = 32; |
Florian Weikert | 2591e19 | 2015-10-05 14:24:51 +0000 | [diff] [blame] | 54 | |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 55 | /** |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 56 | * Creates an instance of {@link BasePrinter} that wraps an existing buffer. |
| 57 | * |
| 58 | * @param buffer an {@link Appendable} |
| 59 | * @return new {@link BasePrinter} |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 60 | */ |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 61 | static BasePrinter getPrinter(Appendable buffer) { |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 62 | return new BasePrinter(buffer); |
Francois-Rene Rideau | c3ad541 | 2015-06-05 22:13:21 +0000 | [diff] [blame] | 63 | } |
| 64 | |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 65 | /** |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 66 | * Creates an instance of {@link BasePrinter} with an empty buffer. |
| 67 | * |
| 68 | * @return new {@link BasePrinter} |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 69 | */ |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 70 | public static BasePrinter getPrinter() { |
tomlu | beafd7e | 2018-04-05 15:03:19 -0700 | [diff] [blame] | 71 | return new BasePrinter(new StringBuilder()); |
| 72 | } |
| 73 | |
| 74 | /** |
Klaus Aehlig | 26c86f8 | 2018-05-29 06:57:03 -0700 | [diff] [blame] | 75 | * Creates an instance of {@link PrettyPrinter} with an empty buffer. |
| 76 | * |
| 77 | * @return new {@link PrettyPrinter} |
| 78 | */ |
| 79 | public static PrettyPrinter getPrettyPrinter() { |
| 80 | return new PrettyPrinter(new StringBuilder()); |
| 81 | } |
| 82 | |
| 83 | /** |
brandjon | e2d1a55 | 2018-04-09 15:50:16 -0700 | [diff] [blame] | 84 | * Creates an instance of {@link BasePrinter} with an empty buffer and whose format strings allow |
| 85 | * only %s and %%. |
tomlu | beafd7e | 2018-04-05 15:03:19 -0700 | [diff] [blame] | 86 | */ |
brandjon | e2d1a55 | 2018-04-09 15:50:16 -0700 | [diff] [blame] | 87 | public static BasePrinter getSimplifiedPrinter() { |
| 88 | return new BasePrinter(new StringBuilder(), /*simplifiedFormatStrings=*/ true); |
Francois-Rene Rideau | c3ad541 | 2015-06-05 22:13:21 +0000 | [diff] [blame] | 89 | } |
| 90 | |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 91 | private Printer() {} |
| 92 | |
| 93 | // These static methods proxy to the similar methods of BasePrinter |
| 94 | |
| 95 | /** |
cparsons | 7ec3f21 | 2018-02-16 14:21:10 -0800 | [diff] [blame] | 96 | * Format an object with Skylark's {@code debugPrint}. |
| 97 | */ |
| 98 | public static String debugPrint(Object x) { |
| 99 | return getPrinter().debugPrint(x).toString(); |
| 100 | } |
| 101 | |
| 102 | /** |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 103 | * Format an object with Skylark's {@code str}. |
| 104 | */ |
Florian Weikert | f07e544 | 2015-07-01 13:08:43 +0000 | [diff] [blame] | 105 | public static String str(Object x) { |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 106 | return getPrinter().str(x).toString(); |
Florian Weikert | f07e544 | 2015-07-01 13:08:43 +0000 | [diff] [blame] | 107 | } |
Francois-Rene Rideau | 304e195 | 2015-08-25 13:30:10 +0000 | [diff] [blame] | 108 | |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 109 | /** |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 110 | * Format an object with Skylark's {@code repr}. |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 111 | */ |
Florian Weikert | f07e544 | 2015-07-01 13:08:43 +0000 | [diff] [blame] | 112 | public static String repr(Object x) { |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 113 | return getPrinter().repr(x).toString(); |
Florian Weikert | 2591e19 | 2015-10-05 14:24:51 +0000 | [diff] [blame] | 114 | } |
| 115 | |
| 116 | /** |
| 117 | * Print a list of object representations. |
| 118 | * |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 119 | * <p>The length of the output will be limited when both {@code maxItemsToPrint} and |
| 120 | * {@code criticalItemsStringLength} have values greater than zero. |
Florian Weikert | 2591e19 | 2015-10-05 14:24:51 +0000 | [diff] [blame] | 121 | * |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 122 | * @param list the list of objects to repr (each as with repr) |
Florian Weikert | 2591e19 | 2015-10-05 14:24:51 +0000 | [diff] [blame] | 123 | * @param before a string to print before the list |
| 124 | * @param separator a separator to print between each object |
| 125 | * @param after a string to print after the list |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 126 | * @param singletonTerminator null or a string to print after the list if it is a singleton The |
| 127 | * singleton case is notably relied upon in python syntax to distinguish a tuple of size one |
| 128 | * such as ("foo",) from a merely parenthesized object such as ("foo"). |
Florian Weikert | 2591e19 | 2015-10-05 14:24:51 +0000 | [diff] [blame] | 129 | * @param maxItemsToPrint the maximum number of elements to be printed. |
| 130 | * @param criticalItemsStringLength a soft limit for the total string length of all arguments. |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 131 | * 'Soft' means that this limit may be exceeded because of formatting. |
| 132 | * @return string representation. |
Florian Weikert | 2591e19 | 2015-10-05 14:24:51 +0000 | [diff] [blame] | 133 | */ |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 134 | public static String printAbbreviatedList( |
| 135 | Iterable<?> list, |
| 136 | String before, |
| 137 | String separator, |
| 138 | String after, |
| 139 | @Nullable String singletonTerminator, |
| 140 | int maxItemsToPrint, |
Florian Weikert | 2591e19 | 2015-10-05 14:24:51 +0000 | [diff] [blame] | 141 | int criticalItemsStringLength) { |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 142 | return new LengthLimitedPrinter() |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 143 | .printAbbreviatedList( |
| 144 | list, |
| 145 | before, |
| 146 | separator, |
| 147 | after, |
| 148 | singletonTerminator, |
| 149 | maxItemsToPrint, |
| 150 | criticalItemsStringLength) |
| 151 | .toString(); |
Florian Weikert | 2591e19 | 2015-10-05 14:24:51 +0000 | [diff] [blame] | 152 | } |
| 153 | |
| 154 | /** |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 155 | * Print a list of object representations. |
| 156 | * |
| 157 | * @param list the list of objects to repr (each as with repr) |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 158 | * @param before a string to print before the list |
| 159 | * @param separator a separator to print between each object |
| 160 | * @param after a string to print after the list |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 161 | * @param singletonTerminator null or a string to print after the list if it is a singleton The |
| 162 | * singleton case is notably relied upon in python syntax to distinguish a tuple of size one |
| 163 | * such as ("foo",) from a merely parenthesized object such as ("foo"). |
| 164 | * @return string representation. |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 165 | */ |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 166 | public static String printAbbreviatedList( |
| 167 | Iterable<?> list, |
| 168 | String before, |
| 169 | String separator, |
| 170 | String after, |
| 171 | @Nullable String singletonTerminator) { |
| 172 | return printAbbreviatedList(list, before, separator, after, singletonTerminator, |
| 173 | SUGGESTED_CRITICAL_LIST_ELEMENTS_COUNT, SUGGESTED_CRITICAL_LIST_ELEMENTS_STRING_LENGTH); |
Florian Weikert | f07e544 | 2015-07-01 13:08:43 +0000 | [diff] [blame] | 174 | } |
| 175 | |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 176 | /** |
| 177 | * Print a list of object representations. |
| 178 | * |
| 179 | * <p>The length of the output will be limited when both {@code maxItemsToPrint} and |
| 180 | * {@code criticalItemsStringLength} have values greater than zero. |
| 181 | * |
| 182 | * @param list the list of objects to repr (each as with repr) |
| 183 | * @param isTuple if true the list will be formatted with parentheses and with a trailing comma |
| 184 | * in case of one-element tuples. |
| 185 | * @param maxItemsToPrint the maximum number of elements to be printed. |
| 186 | * @param criticalItemsStringLength a soft limit for the total string length of all arguments. |
| 187 | * 'Soft' means that this limit may be exceeded because of formatting. |
| 188 | * @return string representation. |
| 189 | */ |
| 190 | public static String printAbbreviatedList( |
| 191 | Iterable<?> list, |
| 192 | boolean isTuple, |
| 193 | int maxItemsToPrint, |
| 194 | int criticalItemsStringLength) { |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 195 | return new LengthLimitedPrinter() |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 196 | .printAbbreviatedList(list, isTuple, maxItemsToPrint, criticalItemsStringLength) |
| 197 | .toString(); |
| 198 | } |
| 199 | |
| 200 | /** |
| 201 | * Perform Python-style string formatting, as per pattern % tuple Limitations: only %d %s %r %% |
| 202 | * are supported. |
| 203 | * |
| 204 | * @param pattern a format string. |
| 205 | * @param arguments an array containing positional arguments. |
| 206 | * @return the formatted string. |
| 207 | */ |
| 208 | public static String format(String pattern, Object... arguments) { |
| 209 | return getPrinter().format(pattern, arguments).toString(); |
| 210 | } |
| 211 | |
| 212 | /** |
| 213 | * Perform Python-style string formatting, as per pattern % tuple Limitations: only %d %s %r %% |
| 214 | * are supported. |
| 215 | * |
| 216 | * @param pattern a format string. |
| 217 | * @param arguments a tuple containing positional arguments. |
| 218 | * @return the formatted string. |
| 219 | */ |
| 220 | public static String formatWithList(String pattern, List<?> arguments) { |
| 221 | return getPrinter().formatWithList(pattern, arguments).toString(); |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 222 | } |
| 223 | |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 224 | /** |
Francois-Rene Rideau | 304e195 | 2015-08-25 13:30:10 +0000 | [diff] [blame] | 225 | * Perform Python-style string formatting, lazily. |
| 226 | * |
| 227 | * @param pattern a format string. |
| 228 | * @param arguments positional arguments. |
| 229 | * @return the formatted string. |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 230 | */ |
Laurent Le Brun | e51a4d2 | 2016-10-11 18:04:16 +0000 | [diff] [blame] | 231 | public static Formattable formattable(final String pattern, Object... arguments) { |
brandjon | f107a53 | 2017-08-16 22:46:02 +0200 | [diff] [blame] | 232 | final List<Object> args = Arrays.asList(arguments); |
Francois-Rene Rideau | 304e195 | 2015-08-25 13:30:10 +0000 | [diff] [blame] | 233 | return new Formattable() { |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 234 | @Override |
| 235 | public String toString() { |
| 236 | return formatWithList(pattern, args); |
| 237 | } |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 238 | |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 239 | @Override |
| 240 | public void formatTo(Formatter formatter, int flags, int width, int precision) { |
| 241 | Printer.getPrinter(formatter.out()).formatWithList(pattern, args); |
| 242 | } |
| 243 | }; |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 244 | } |
| 245 | |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 246 | /** |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 247 | * Append a char to a buffer. In case of {@link IOException} throw an {@link AssertionError} |
| 248 | * instead |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 249 | * |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 250 | * @return buffer |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 251 | */ |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 252 | public static Appendable append(Appendable buffer, char c) { |
| 253 | try { |
| 254 | return buffer.append(c); |
| 255 | } catch (IOException e) { |
| 256 | throw new AssertionError(e); |
Francois-Rene Rideau | 8d5cce3 | 2015-06-16 23:12:04 +0000 | [diff] [blame] | 257 | } |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 258 | } |
| 259 | |
| 260 | /** |
| 261 | * Append a char sequence to a buffer. In case of {@link IOException} throw an |
| 262 | * {@link AssertionError} instead |
| 263 | * |
| 264 | * @return buffer |
| 265 | */ |
| 266 | public static Appendable append(Appendable buffer, CharSequence s) { |
| 267 | try { |
| 268 | return buffer.append(s); |
| 269 | } catch (IOException e) { |
| 270 | throw new AssertionError(e); |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 271 | } |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 272 | } |
| 273 | |
| 274 | /** |
| 275 | * Append a char sequence range to a buffer. In case of {@link IOException} throw an |
| 276 | * {@link AssertionError} instead |
| 277 | * @return buffer |
| 278 | */ |
| 279 | private static Appendable append(Appendable buffer, CharSequence s, int start, int end) { |
| 280 | try { |
| 281 | return buffer.append(s, start, end); |
| 282 | } catch (IOException e) { |
| 283 | throw new AssertionError(e); |
| 284 | } |
Francois-Rene Rideau | d61f531 | 2015-06-13 03:34:47 +0000 | [diff] [blame] | 285 | } |
Florian Weikert | 2591e19 | 2015-10-05 14:24:51 +0000 | [diff] [blame] | 286 | |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 287 | /** Actual class that implements Printer API */ |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 288 | public static class BasePrinter implements SkylarkPrinter { |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 289 | // Methods of this class should not recurse through static methods of Printer |
| 290 | |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 291 | protected final Appendable buffer; |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 292 | |
| 293 | /** |
brandjon | a9cafcb | 2018-04-04 12:24:33 -0700 | [diff] [blame] | 294 | * If true, the only percent sequences allowed in format strings are %s substitutions and %% |
| 295 | * escapes. |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 296 | */ |
brandjon | a9cafcb | 2018-04-04 12:24:33 -0700 | [diff] [blame] | 297 | protected final boolean simplifiedFormatStrings; |
| 298 | |
| 299 | /** |
| 300 | * Creates a printer. |
| 301 | * |
| 302 | * @param buffer the {@link Appendable} that will be written to |
| 303 | * @param simplifiedFormatStrings if true, format strings will allow only %s and %% |
| 304 | */ |
| 305 | protected BasePrinter(Appendable buffer, boolean simplifiedFormatStrings) { |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 306 | this.buffer = buffer; |
brandjon | a9cafcb | 2018-04-04 12:24:33 -0700 | [diff] [blame] | 307 | this.simplifiedFormatStrings = simplifiedFormatStrings; |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 308 | } |
| 309 | |
brandjon | a9cafcb | 2018-04-04 12:24:33 -0700 | [diff] [blame] | 310 | /** |
| 311 | * Creates a printer that writes to the given buffer and that does not use simplified format |
| 312 | * strings. |
| 313 | */ |
| 314 | protected BasePrinter(Appendable buffer) { |
| 315 | this(buffer, /*simplifiedFormatStrings=*/ false); |
| 316 | } |
| 317 | |
| 318 | /** |
| 319 | * Creates a printer that uses a fresh buffer and that does not use simplified format strings. |
| 320 | */ |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 321 | protected BasePrinter() { |
brandjon | a9cafcb | 2018-04-04 12:24:33 -0700 | [diff] [blame] | 322 | this(new StringBuilder()); |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 323 | } |
| 324 | |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 325 | @Override |
| 326 | public String toString() { |
| 327 | return buffer.toString(); |
| 328 | } |
| 329 | |
| 330 | /** |
cparsons | 7ec3f21 | 2018-02-16 14:21:10 -0800 | [diff] [blame] | 331 | * Print an informal debug-only representation of object x. |
| 332 | * |
| 333 | * @param o the object |
| 334 | * @return the buffer, in fluent style |
| 335 | */ |
| 336 | public BasePrinter debugPrint(Object o) { |
| 337 | if (o instanceof SkylarkValue) { |
| 338 | ((SkylarkValue) o).debugPrint(this); |
| 339 | return this; |
| 340 | } |
| 341 | |
| 342 | return this.str(o); |
| 343 | } |
| 344 | |
| 345 | /** |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 346 | * Print an informal representation of object x. Currently only differs from repr in the |
| 347 | * behavior for strings and labels at top-level, that are returned as is rather than quoted. |
| 348 | * |
| 349 | * @param o the object |
| 350 | * @return the buffer, in fluent style |
| 351 | */ |
| 352 | public BasePrinter str(Object o) { |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 353 | if (o instanceof SkylarkValue) { |
| 354 | ((SkylarkValue) o).str(this); |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 355 | return this; |
| 356 | } |
| 357 | |
| 358 | if (o instanceof String) { |
| 359 | return this.append((String) o); |
| 360 | } |
| 361 | return this.repr(o); |
| 362 | } |
| 363 | |
| 364 | /** |
| 365 | * Print an official representation of object x. For regular data structures, the value should |
| 366 | * be parsable back into an equal data structure. |
| 367 | * |
| 368 | * @param o the string a representation of which to repr. |
| 369 | * @return BasePrinter. |
| 370 | */ |
| 371 | @Override |
| 372 | public BasePrinter repr(Object o) { |
| 373 | if (o == null) { |
brandjon | f107a53 | 2017-08-16 22:46:02 +0200 | [diff] [blame] | 374 | // Java null is not a valid Skylark value, but sometimes printers are used on non-Skylark |
| 375 | // values such as Locations or ASTs. |
| 376 | this.append("null"); |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 377 | |
tomlu | 67c84b1 | 2017-11-06 19:49:16 +0100 | [diff] [blame] | 378 | } else if (o instanceof SkylarkPrintable) { |
| 379 | ((SkylarkPrintable) o).repr(this); |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 380 | |
| 381 | } else if (o instanceof String) { |
| 382 | writeString((String) o); |
| 383 | |
| 384 | } else if (o instanceof Integer || o instanceof Double) { |
| 385 | this.append(o.toString()); |
| 386 | |
vladmos | f07773b | 2017-07-11 16:31:32 +0200 | [diff] [blame] | 387 | } else if (Boolean.TRUE.equals(o)) { |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 388 | this.append("True"); |
| 389 | |
vladmos | f07773b | 2017-07-11 16:31:32 +0200 | [diff] [blame] | 390 | } else if (Boolean.FALSE.equals(o)) { |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 391 | this.append("False"); |
| 392 | |
| 393 | } else if (o instanceof Map<?, ?>) { |
| 394 | Map<?, ?> dict = (Map<?, ?>) o; |
| 395 | this.printList(dict.entrySet(), "{", ", ", "}", null); |
| 396 | |
| 397 | } else if (o instanceof List<?>) { |
| 398 | List<?> seq = (List<?>) o; |
| 399 | this.printList(seq, false); |
| 400 | |
| 401 | } else if (o instanceof Map.Entry<?, ?>) { |
| 402 | Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o; |
| 403 | this.repr(entry.getKey()); |
| 404 | this.append(": "); |
| 405 | this.repr(entry.getValue()); |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 406 | } else if (o instanceof Class<?>) { |
| 407 | this.append(EvalUtils.getDataTypeNameFromClass((Class<?>) o)); |
| 408 | |
Googler | 4ace465 | 2019-09-16 07:47:08 -0700 | [diff] [blame] | 409 | } else if (o instanceof Node || o instanceof Location) { |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 410 | // AST node objects and locations are printed in tracebacks and error messages, |
| 411 | // it's safe to print their toString representations |
| 412 | this.append(o.toString()); |
| 413 | |
| 414 | } else { |
vladmos | d4cc4b6 | 2017-07-14 19:04:55 +0200 | [diff] [blame] | 415 | // Other types of objects shouldn't be leaked to Skylark, but if happens, their |
| 416 | // .toString method shouldn't be used because their return values are likely to contain |
| 417 | // memory addresses or other nondeterministic information. |
| 418 | this.append("<unknown object " + o.getClass().getName() + ">"); |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 419 | } |
| 420 | |
| 421 | return this; |
| 422 | } |
| 423 | |
| 424 | /** |
| 425 | * Write a properly escaped Skylark representation of a string to a buffer. |
| 426 | * |
| 427 | * @param s the string a representation of which to repr. |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 428 | * @return this printer. |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 429 | */ |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 430 | protected BasePrinter writeString(String s) { |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 431 | this.append(SKYLARK_QUOTATION_MARK); |
| 432 | int len = s.length(); |
| 433 | for (int i = 0; i < len; i++) { |
| 434 | char c = s.charAt(i); |
| 435 | escapeCharacter(c); |
| 436 | } |
| 437 | return this.append(SKYLARK_QUOTATION_MARK); |
| 438 | } |
| 439 | |
| 440 | private BasePrinter backslashChar(char c) { |
| 441 | return this.append('\\').append(c); |
| 442 | } |
| 443 | |
| 444 | private BasePrinter escapeCharacter(char c) { |
| 445 | if (c == SKYLARK_QUOTATION_MARK) { |
| 446 | return backslashChar(c); |
| 447 | } |
| 448 | switch (c) { |
| 449 | case '\\': |
| 450 | return backslashChar('\\'); |
| 451 | case '\r': |
| 452 | return backslashChar('r'); |
| 453 | case '\n': |
| 454 | return backslashChar('n'); |
| 455 | case '\t': |
| 456 | return backslashChar('t'); |
| 457 | default: |
| 458 | if (c < 32) { |
| 459 | //TODO(bazel-team): support \x escapes |
| 460 | return this.append(String.format("\\x%02x", (int) c)); |
| 461 | } |
| 462 | return this.append(c); // no need to support UTF-8 |
| 463 | } // endswitch |
| 464 | } |
| 465 | |
| 466 | /** |
| 467 | * Print a list of object representations |
| 468 | * |
| 469 | * @param list the list of objects to repr (each as with repr) |
| 470 | * @param before a string to print before the list items, e.g. an opening bracket |
| 471 | * @param separator a separator to print between items |
| 472 | * @param after a string to print after the list items, e.g. a closing bracket |
| 473 | * @param singletonTerminator null or a string to print after the list if it is a singleton The |
| 474 | * singleton case is notably relied upon in python syntax to distinguish a tuple of size one |
| 475 | * such as ("foo",) from a merely parenthesized object such as ("foo"). |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 476 | * @return this printer. |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 477 | */ |
| 478 | @Override |
| 479 | public BasePrinter printList( |
| 480 | Iterable<?> list, |
| 481 | String before, |
| 482 | String separator, |
| 483 | String after, |
| 484 | @Nullable String singletonTerminator) { |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 485 | |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 486 | this.append(before); |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 487 | int len = appendListElements(list, separator); |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 488 | if (singletonTerminator != null && len == 1) { |
| 489 | this.append(singletonTerminator); |
| 490 | } |
| 491 | return this.append(after); |
| 492 | } |
| 493 | |
| 494 | /** |
| 495 | * Appends the given elements to the specified {@link Appendable} and returns the number of |
| 496 | * elements. |
| 497 | */ |
| 498 | private int appendListElements(Iterable<?> list, String separator) { |
| 499 | boolean printSeparator = false; // don't print the separator before the first element |
| 500 | int len = 0; |
| 501 | for (Object o : list) { |
| 502 | if (printSeparator) { |
| 503 | this.append(separator); |
| 504 | } |
| 505 | this.repr(o); |
| 506 | printSeparator = true; |
| 507 | len++; |
| 508 | } |
| 509 | return len; |
| 510 | } |
| 511 | |
| 512 | /** |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 513 | * Print a Skylark list or tuple of object representations |
| 514 | * |
| 515 | * @param list the contents of the list or tuple |
| 516 | * @param isTuple if true the list will be formatted with parentheses and with a trailing comma |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 517 | * in case of one-element tuples. 'Soft' means that this limit may be exceeded because of |
| 518 | * formatting. |
| 519 | * @return this printer. |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 520 | */ |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 521 | @Override |
| 522 | public BasePrinter printList(Iterable<?> list, boolean isTuple) { |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 523 | if (isTuple) { |
| 524 | return this.printList(list, "(", ", ", ")", ","); |
| 525 | } else { |
| 526 | return this.printList(list, "[", ", ", "]", null); |
| 527 | } |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 528 | } |
| 529 | |
| 530 | /** |
brandjon | a9cafcb | 2018-04-04 12:24:33 -0700 | [diff] [blame] | 531 | * Perform Python-style string formatting, similar to the {@code pattern % tuple} syntax. |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 532 | * |
brandjon | a9cafcb | 2018-04-04 12:24:33 -0700 | [diff] [blame] | 533 | * <p>The only supported placeholder patterns are |
| 534 | * <ul> |
| 535 | * <li>{@code %s} (convert as if by {@code str()}) |
| 536 | * <li>{@code %r} (convert as if by {@code repr()}) |
| 537 | * <li>{@code %d} (convert an integer to its decimal representation) |
| 538 | * </ul> |
| 539 | * To encode a literal percent character, escape it as {@code %%}. It is an error to have a |
| 540 | * non-escaped {@code %} at the end of the string or followed by any character not listed above. |
| 541 | * |
| 542 | * <p>If this printer has {@code simplifiedFormatStrings} set, only {@code %s} and {@code %%} |
| 543 | * are permitted. |
| 544 | * |
| 545 | * @param pattern a format string that may contain placeholders |
| 546 | * @param arguments an array containing arguments to substitute into the placeholders in order |
| 547 | * @return the formatted string |
| 548 | * @throws IllegalFormatException if {@code pattern} is not a valid format string, or if |
| 549 | * {@code arguments} mismatches the number or type of placeholders in {@code pattern} |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 550 | */ |
| 551 | @Override |
| 552 | public BasePrinter format(String pattern, Object... arguments) { |
brandjon | f107a53 | 2017-08-16 22:46:02 +0200 | [diff] [blame] | 553 | return this.formatWithList(pattern, Arrays.asList(arguments)); |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 554 | } |
| 555 | |
| 556 | /** |
brandjon | a9cafcb | 2018-04-04 12:24:33 -0700 | [diff] [blame] | 557 | * Perform Python-style string formatting, similar to the {@code pattern % tuple} syntax. |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 558 | * |
brandjon | a9cafcb | 2018-04-04 12:24:33 -0700 | [diff] [blame] | 559 | * <p>Same as {@link #format(String, Object...)}, but with a list instead of variadic args. |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 560 | */ |
| 561 | @Override |
| 562 | public BasePrinter formatWithList(String pattern, List<?> arguments) { |
| 563 | // TODO(bazel-team): support formatting arguments, and more complex Python patterns. |
| 564 | // N.B. MissingFormatWidthException is the only kind of IllegalFormatException |
| 565 | // whose constructor can take and display arbitrary error message, hence its use below. |
| 566 | |
| 567 | int length = pattern.length(); |
| 568 | int argLength = arguments.size(); |
| 569 | int i = 0; // index of next character in pattern |
| 570 | int a = 0; // index of next argument in arguments |
| 571 | |
| 572 | while (i < length) { |
| 573 | int p = pattern.indexOf('%', i); |
| 574 | if (p == -1) { |
| 575 | Printer.append(buffer, pattern, i, length); |
| 576 | break; |
| 577 | } |
| 578 | if (p > i) { |
| 579 | Printer.append(buffer, pattern, i, p); |
| 580 | } |
| 581 | if (p == length - 1) { |
| 582 | throw new MissingFormatWidthException( |
| 583 | "incomplete format pattern ends with %: " + this.repr(pattern)); |
| 584 | } |
| 585 | char directive = pattern.charAt(p + 1); |
| 586 | i = p + 2; |
| 587 | switch (directive) { |
| 588 | case '%': |
| 589 | this.append('%'); |
| 590 | continue; |
| 591 | case 'd': |
| 592 | case 'r': |
| 593 | case 's': |
brandjon | a9cafcb | 2018-04-04 12:24:33 -0700 | [diff] [blame] | 594 | if (simplifiedFormatStrings && (directive != 's')) { |
| 595 | throw new UnknownFormatConversionException( |
| 596 | "cannot use %" + directive + " substitution placeholder when " |
| 597 | + "simplifiedFormatStrings is set"); |
| 598 | } |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 599 | if (a >= argLength) { |
| 600 | throw new MissingFormatWidthException( |
| 601 | "not enough arguments for format pattern " |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 602 | + Printer.repr(pattern) |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 603 | + ": " |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 604 | + Printer.repr(Tuple.copyOf(arguments))); |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 605 | } |
| 606 | Object argument = arguments.get(a++); |
| 607 | switch (directive) { |
| 608 | case 'd': |
| 609 | if (argument instanceof Integer) { |
| 610 | this.append(argument.toString()); |
| 611 | continue; |
| 612 | } else { |
| 613 | throw new MissingFormatWidthException( |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 614 | "invalid argument " + Printer.repr(argument) + " for format pattern %d"); |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 615 | } |
| 616 | case 'r': |
| 617 | this.repr(argument); |
| 618 | continue; |
| 619 | case 's': |
| 620 | this.str(argument); |
| 621 | continue; |
| 622 | } |
| 623 | // fall through |
| 624 | default: |
| 625 | throw new MissingFormatWidthException( |
| 626 | // The call to Printer.repr doesn't cause an infinite recursion because it's |
| 627 | // only used to format a string properly |
| 628 | String.format("unsupported format character \"%s\" at index %s in %s", |
| 629 | String.valueOf(directive), p + 1, Printer.repr(pattern))); |
| 630 | } |
| 631 | } |
| 632 | if (a < argLength) { |
| 633 | throw new MissingFormatWidthException( |
| 634 | "not all arguments converted during string formatting"); |
| 635 | } |
| 636 | return this; |
| 637 | } |
| 638 | |
| 639 | @Override |
| 640 | public BasePrinter append(char c) { |
| 641 | Printer.append(buffer, c); |
| 642 | return this; |
| 643 | } |
| 644 | |
| 645 | @Override |
| 646 | public BasePrinter append(CharSequence s) { |
| 647 | Printer.append(buffer, s); |
| 648 | return this; |
| 649 | } |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 650 | |
| 651 | BasePrinter append(CharSequence sequence, int start, int end) { |
| 652 | return this.append(sequence.subSequence(start, end)); |
| 653 | } |
| 654 | } |
| 655 | |
jingwen | 68c57f0 | 2018-11-21 16:17:17 -0800 | [diff] [blame] | 656 | /** A printer that breaks lines between the entries of lists, with proper indenting. */ |
| 657 | public static class PrettyPrinter extends BasePrinter { |
Klaus Aehlig | 26c86f8 | 2018-05-29 06:57:03 -0700 | [diff] [blame] | 658 | static final int BASE_INDENT = 4; |
| 659 | private int indent; |
| 660 | |
| 661 | protected PrettyPrinter(Appendable buffer) { |
| 662 | super(buffer); |
| 663 | indent = 0; |
| 664 | } |
| 665 | |
| 666 | @Override |
| 667 | public BasePrinter printList( |
| 668 | Iterable<?> list, |
| 669 | String before, |
| 670 | String untrimmedSeparator, |
| 671 | String after, |
| 672 | @Nullable String singletonTerminator) { |
| 673 | |
Klaus Aehlig | 3695591 | 2018-06-14 08:17:33 -0700 | [diff] [blame] | 674 | // If the list is empty, do not split the presentation over |
| 675 | // several lines. |
| 676 | if (!list.iterator().hasNext()) { |
| 677 | this.append(before + after); |
| 678 | return this; |
| 679 | } |
| 680 | |
Klaus Aehlig | 26c86f8 | 2018-05-29 06:57:03 -0700 | [diff] [blame] | 681 | String separator = untrimmedSeparator.trim(); |
| 682 | |
| 683 | this.append(before + "\n"); |
| 684 | indent += BASE_INDENT; |
| 685 | boolean printSeparator = false; // don't print the separator before the first element |
| 686 | int len = 0; |
| 687 | for (Object o : list) { |
| 688 | if (printSeparator) { |
| 689 | this.append(separator + "\n"); |
| 690 | } |
| 691 | this.append(Strings.repeat(" ", indent)); |
| 692 | this.repr(o); |
| 693 | printSeparator = true; |
| 694 | len++; |
| 695 | } |
| 696 | if (singletonTerminator != null && len == 1) { |
| 697 | this.append(singletonTerminator); |
| 698 | } |
| 699 | this.append("\n"); |
| 700 | indent -= BASE_INDENT; |
| 701 | this.append(Strings.repeat(" ", indent) + after); |
| 702 | return this; |
| 703 | } |
| 704 | } |
| 705 | |
vladmos | 6ff634d | 2017-07-05 10:25:01 -0400 | [diff] [blame] | 706 | /** A version of {@code BasePrinter} that is able to print abbreviated lists. */ |
| 707 | public static final class LengthLimitedPrinter extends BasePrinter { |
| 708 | |
| 709 | private static final ImmutableSet<Character> SPECIAL_CHARS = |
| 710 | ImmutableSet.of(',', ' ', '"', '\'', ':', '(', ')', '[', ']', '{', '}'); |
| 711 | |
| 712 | private static final Pattern ARGS_PATTERN = Pattern.compile("<\\d+ more arguments>"); |
| 713 | |
| 714 | // Limits can be set several times recursively and then unset the same amount of times. |
| 715 | // But in fact they should be set only the first time and unset only the last time. |
| 716 | // To achieve that we need to keep track of the recursion depth. |
| 717 | private int recursionDepth; |
| 718 | // Current limit of symbols to print in the limited mode (`ignoreLimit = false`). |
| 719 | private int limit; |
| 720 | private boolean ignoreLimit = true; |
| 721 | private boolean previouslyShortened; |
| 722 | |
| 723 | /** |
| 724 | * Print a list of object representations. |
| 725 | * |
| 726 | * <p>The length of the output will be limited when both {@code maxItemsToPrint} and {@code |
| 727 | * criticalItemsStringLength} have values greater than zero. |
| 728 | * |
| 729 | * @param list the list of objects to repr (each as with repr) |
| 730 | * @param before a string to print before the list |
| 731 | * @param separator a separator to print between each object |
| 732 | * @param after a string to print after the list |
| 733 | * @param singletonTerminator null or a string to print after the list if it is a singleton The |
| 734 | * singleton case is notably relied upon in python syntax to distinguish a tuple of size one |
| 735 | * such as ("foo",) from a merely parenthesized object such as ("foo"). |
| 736 | * @param maxItemsToPrint the maximum number of elements to be printed. |
| 737 | * @param criticalItemsStringLength a soft limit for the total string length of all arguments. |
| 738 | * 'Soft' means that this limit may be exceeded because of formatting. |
| 739 | * @return the BasePrinter. |
| 740 | */ |
| 741 | LengthLimitedPrinter printAbbreviatedList( |
| 742 | Iterable<?> list, |
| 743 | String before, |
| 744 | String separator, |
| 745 | String after, |
| 746 | @Nullable String singletonTerminator, |
| 747 | int maxItemsToPrint, |
| 748 | int criticalItemsStringLength) { |
| 749 | this.append(before); |
| 750 | int len = appendListElements(list, separator, maxItemsToPrint, criticalItemsStringLength); |
| 751 | if (singletonTerminator != null && len == 1) { |
| 752 | this.append(singletonTerminator); |
| 753 | } |
| 754 | return this.append(after); |
| 755 | } |
| 756 | |
| 757 | /** |
| 758 | * Print a Skylark list or tuple of object representations |
| 759 | * |
| 760 | * @param list the contents of the list or tuple |
| 761 | * @param isTuple if true the list will be formatted with parentheses and with a trailing comma |
| 762 | * in case of one-element tuples. |
| 763 | * @param maxItemsToPrint the maximum number of elements to be printed. |
| 764 | * @param criticalItemsStringLength a soft limit for the total string length of all arguments. |
| 765 | * 'Soft' means that this limit may be exceeded because of formatting. |
| 766 | * @return this printer. |
| 767 | */ |
| 768 | public LengthLimitedPrinter printAbbreviatedList( |
| 769 | Iterable<?> list, boolean isTuple, int maxItemsToPrint, int criticalItemsStringLength) { |
| 770 | if (isTuple) { |
| 771 | return this.printAbbreviatedList( |
| 772 | list, "(", ", ", ")", ",", maxItemsToPrint, criticalItemsStringLength); |
| 773 | } else { |
| 774 | return this.printAbbreviatedList( |
| 775 | list, "[", ", ", "]", null, maxItemsToPrint, criticalItemsStringLength); |
| 776 | } |
| 777 | } |
| 778 | |
| 779 | /** |
| 780 | * Tries to append the given elements to the specified {@link Appendable} until specific limits |
| 781 | * are reached. |
| 782 | * |
| 783 | * @return the number of appended elements. |
| 784 | */ |
| 785 | private int appendListElements( |
| 786 | Iterable<?> list, String separator, int maxItemsToPrint, int criticalItemsStringLength) { |
| 787 | boolean printSeparator = false; // don't print the separator before the first element |
| 788 | boolean skipArgs = false; |
| 789 | int items = Iterables.size(list); |
| 790 | int len = 0; |
| 791 | // We don't want to print "1 more arguments", hence we don't skip arguments if there is only |
| 792 | // one above the limit. |
| 793 | int itemsToPrint = (items - maxItemsToPrint == 1) ? items : maxItemsToPrint; |
| 794 | enforceLimit(criticalItemsStringLength); |
| 795 | for (Object o : list) { |
| 796 | // We don't want to print "1 more arguments", even if we hit the string limit. |
| 797 | if (len == itemsToPrint || (hasHitLimit() && len < items - 1)) { |
| 798 | skipArgs = true; |
| 799 | break; |
| 800 | } |
| 801 | if (printSeparator) { |
| 802 | this.append(separator); |
| 803 | } |
| 804 | this.repr(o); |
| 805 | printSeparator = true; |
| 806 | len++; |
| 807 | } |
| 808 | ignoreLimit(); |
| 809 | if (skipArgs) { |
| 810 | this.append(separator); |
| 811 | this.append(String.format("<%d more arguments>", items - len)); |
| 812 | } |
| 813 | return len; |
| 814 | } |
| 815 | |
| 816 | @Override |
| 817 | public LengthLimitedPrinter append(CharSequence csq) { |
| 818 | if (ignoreLimit || hasOnlySpecialChars(csq)) { |
| 819 | // Don't update limit. |
| 820 | Printer.append(buffer, csq); |
| 821 | previouslyShortened = false; |
| 822 | } else { |
| 823 | int length = csq.length(); |
| 824 | if (length <= limit) { |
| 825 | limit -= length; |
| 826 | Printer.append(buffer, csq); |
| 827 | } else { |
| 828 | Printer.append(buffer, csq, 0, limit); |
| 829 | // We don't want to append multiple ellipses. |
| 830 | if (!previouslyShortened) { |
| 831 | Printer.append(buffer, "..."); |
| 832 | } |
| 833 | appendTrailingSpecialChars(csq, limit); |
| 834 | previouslyShortened = true; |
| 835 | limit = 0; |
| 836 | } |
| 837 | } |
| 838 | return this; |
| 839 | } |
| 840 | |
| 841 | @Override |
| 842 | public LengthLimitedPrinter append(char c) { |
| 843 | // Use the local `append(sequence)` method so that limits can apply |
| 844 | return this.append(String.valueOf(c)); |
| 845 | } |
| 846 | |
| 847 | /** |
| 848 | * Appends any trailing "special characters" (e.g. brackets, quotation marks) in the given |
| 849 | * sequence to the output buffer, regardless of the limit. |
| 850 | * |
| 851 | * <p>For example, let's look at foo(['too long']). Without this method, the shortened result |
| 852 | * would be foo(['too...) instead of the prettier foo(['too...']). |
| 853 | * |
| 854 | * <p>If the input string was already shortened and contains "<x more arguments>", this part |
| 855 | * will also be appended. |
| 856 | */ |
| 857 | // TODO(bazel-team): Given an input list |
| 858 | // |
| 859 | // [1, 2, 3, [10, 20, 30, 40, 50, 60], 4, 5, 6] |
| 860 | // |
| 861 | // the inner list gets doubly mangled as |
| 862 | // |
| 863 | // [1, 2, 3, [10, 20, 30, 40, <2 more argu...<2 more arguments>], <3 more arguments>] |
| 864 | private LengthLimitedPrinter appendTrailingSpecialChars(CharSequence csq, int limit) { |
| 865 | int length = csq.length(); |
| 866 | Matcher matcher = ARGS_PATTERN.matcher(csq); |
| 867 | // We assume that everything following the "x more arguments" part has to be copied, too. |
| 868 | int start = matcher.find() ? matcher.start() : length; |
| 869 | // Find the left-most non-arg char that has to be copied. |
| 870 | for (int i = start - 1; i > limit; --i) { |
| 871 | if (isSpecialChar(csq.charAt(i))) { |
| 872 | start = i; |
| 873 | } else { |
| 874 | break; |
| 875 | } |
| 876 | } |
| 877 | if (start < length) { |
| 878 | Printer.append(buffer, csq, start, csq.length()); |
| 879 | } |
| 880 | return this; |
| 881 | } |
| 882 | |
| 883 | /** |
| 884 | * Returns whether the given sequence denotes characters that are not part of the value of an |
| 885 | * argument. |
| 886 | * |
| 887 | * <p>Examples are brackets, braces and quotation marks. |
| 888 | */ |
| 889 | private boolean hasOnlySpecialChars(CharSequence csq) { |
| 890 | for (int i = 0; i < csq.length(); ++i) { |
| 891 | if (!isSpecialChar(csq.charAt(i))) { |
| 892 | return false; |
| 893 | } |
| 894 | } |
| 895 | return true; |
| 896 | } |
| 897 | |
| 898 | private boolean isSpecialChar(char c) { |
| 899 | return SPECIAL_CHARS.contains(c); |
| 900 | } |
| 901 | |
| 902 | boolean hasHitLimit() { |
| 903 | return limit <= 0; |
| 904 | } |
| 905 | |
| 906 | private void enforceLimit(int limit) { |
| 907 | ignoreLimit = false; |
| 908 | if (recursionDepth == 0) { |
| 909 | this.limit = limit; |
| 910 | ++recursionDepth; |
| 911 | } |
| 912 | } |
| 913 | |
| 914 | private void ignoreLimit() { |
| 915 | if (recursionDepth > 0) { |
| 916 | --recursionDepth; |
| 917 | } |
| 918 | if (recursionDepth == 0) { |
| 919 | ignoreLimit = true; |
| 920 | } |
| 921 | } |
vladmos | 4690793 | 2017-06-30 14:01:45 +0200 | [diff] [blame] | 922 | } |
Francois-Rene Rideau | c3ad541 | 2015-06-05 22:13:21 +0000 | [diff] [blame] | 923 | } |