| // Copyright 2015 The Bazel Authors. All rights reserved. |
| // |
| // Licensed 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 com.google.devtools.build.lib.syntax; |
| |
| import com.google.common.base.Strings; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.events.Location; |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkPrintable; |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter; |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkValue; |
| import com.google.devtools.build.lib.syntax.SkylarkList.Tuple; |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.Formattable; |
| import java.util.Formatter; |
| import java.util.IllegalFormatException; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.MissingFormatWidthException; |
| import java.util.UnknownFormatConversionException; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import javax.annotation.Nullable; |
| |
| /** (Pretty) Printing of Skylark values */ |
| public class Printer { |
| |
| public static final char SKYLARK_QUOTATION_MARK = '"'; |
| |
| /* |
| * Suggested maximum number of list elements that should be printed via printAbbreviatedList(). |
| * By default, this setting is not considered and no limitation takes place. |
| */ |
| public static final int SUGGESTED_CRITICAL_LIST_ELEMENTS_COUNT = 4; |
| |
| /* |
| * Suggested limit for printAbbreviatedList() to shorten the values of list elements when |
| * their combined string length reaches this value. |
| * By default, this setting is not considered and no limitation takes place. |
| */ |
| public static final int SUGGESTED_CRITICAL_LIST_ELEMENTS_STRING_LENGTH = 32; |
| |
| /** |
| * Creates an instance of {@link BasePrinter} that wraps an existing buffer. |
| * |
| * @param buffer an {@link Appendable} |
| * @return new {@link BasePrinter} |
| */ |
| static BasePrinter getPrinter(Appendable buffer) { |
| return new BasePrinter(buffer); |
| } |
| |
| /** |
| * Creates an instance of {@link BasePrinter} with an empty buffer. |
| * |
| * @return new {@link BasePrinter} |
| */ |
| public static BasePrinter getPrinter() { |
| return new BasePrinter(new StringBuilder()); |
| } |
| |
| /** |
| * Creates an instance of {@link PrettyPrinter} with an empty buffer. |
| * |
| * @return new {@link PrettyPrinter} |
| */ |
| public static PrettyPrinter getPrettyPrinter() { |
| return new PrettyPrinter(new StringBuilder()); |
| } |
| |
| /** |
| * Creates an instance of {@link WorkspacePrettyPrinter} with an empty buffer. |
| * |
| * @return new {@link WorkspacePrettyPrinter} |
| */ |
| public static WorkspacePrettyPrinter getWorkspacePrettyPrinter() { |
| return new WorkspacePrettyPrinter(new StringBuilder()); |
| } |
| |
| /** |
| * Creates an instance of {@link BasePrinter} with an empty buffer and whose format strings allow |
| * only %s and %%. |
| */ |
| public static BasePrinter getSimplifiedPrinter() { |
| return new BasePrinter(new StringBuilder(), /*simplifiedFormatStrings=*/ true); |
| } |
| |
| private Printer() {} |
| |
| // These static methods proxy to the similar methods of BasePrinter |
| |
| /** |
| * Format an object with Skylark's {@code debugPrint}. |
| */ |
| public static String debugPrint(Object x) { |
| return getPrinter().debugPrint(x).toString(); |
| } |
| |
| /** |
| * Format an object with Skylark's {@code str}. |
| */ |
| public static String str(Object x) { |
| return getPrinter().str(x).toString(); |
| } |
| |
| /** |
| * Format an object with Skylark's {@code repr}. |
| */ |
| public static String repr(Object x) { |
| return getPrinter().repr(x).toString(); |
| } |
| |
| /** |
| * Print a list of object representations. |
| * |
| * <p>The length of the output will be limited when both {@code maxItemsToPrint} and |
| * {@code criticalItemsStringLength} have values greater than zero. |
| * |
| * @param list the list of objects to repr (each as with repr) |
| * @param before a string to print before the list |
| * @param separator a separator to print between each object |
| * @param after a string to print after the list |
| * @param singletonTerminator null or a string to print after the list if it is a singleton The |
| * singleton case is notably relied upon in python syntax to distinguish a tuple of size one |
| * such as ("foo",) from a merely parenthesized object such as ("foo"). |
| * @param maxItemsToPrint the maximum number of elements to be printed. |
| * @param criticalItemsStringLength a soft limit for the total string length of all arguments. |
| * 'Soft' means that this limit may be exceeded because of formatting. |
| * @return string representation. |
| */ |
| public static String printAbbreviatedList( |
| Iterable<?> list, |
| String before, |
| String separator, |
| String after, |
| @Nullable String singletonTerminator, |
| int maxItemsToPrint, |
| int criticalItemsStringLength) { |
| return new LengthLimitedPrinter() |
| .printAbbreviatedList( |
| list, |
| before, |
| separator, |
| after, |
| singletonTerminator, |
| maxItemsToPrint, |
| criticalItemsStringLength) |
| .toString(); |
| } |
| |
| /** |
| * Print a list of object representations. |
| * |
| * @param list the list of objects to repr (each as with repr) |
| * @param before a string to print before the list |
| * @param separator a separator to print between each object |
| * @param after a string to print after the list |
| * @param singletonTerminator null or a string to print after the list if it is a singleton The |
| * singleton case is notably relied upon in python syntax to distinguish a tuple of size one |
| * such as ("foo",) from a merely parenthesized object such as ("foo"). |
| * @return string representation. |
| */ |
| public static String printAbbreviatedList( |
| Iterable<?> list, |
| String before, |
| String separator, |
| String after, |
| @Nullable String singletonTerminator) { |
| return printAbbreviatedList(list, before, separator, after, singletonTerminator, |
| SUGGESTED_CRITICAL_LIST_ELEMENTS_COUNT, SUGGESTED_CRITICAL_LIST_ELEMENTS_STRING_LENGTH); |
| } |
| |
| /** |
| * Print a list of object representations. |
| * |
| * <p>The length of the output will be limited when both {@code maxItemsToPrint} and |
| * {@code criticalItemsStringLength} have values greater than zero. |
| * |
| * @param list the list of objects to repr (each as with repr) |
| * @param isTuple if true the list will be formatted with parentheses and with a trailing comma |
| * in case of one-element tuples. |
| * @param maxItemsToPrint the maximum number of elements to be printed. |
| * @param criticalItemsStringLength a soft limit for the total string length of all arguments. |
| * 'Soft' means that this limit may be exceeded because of formatting. |
| * @return string representation. |
| */ |
| public static String printAbbreviatedList( |
| Iterable<?> list, |
| boolean isTuple, |
| int maxItemsToPrint, |
| int criticalItemsStringLength) { |
| return new LengthLimitedPrinter() |
| .printAbbreviatedList(list, isTuple, maxItemsToPrint, criticalItemsStringLength) |
| .toString(); |
| } |
| |
| /** |
| * Perform Python-style string formatting, as per pattern % tuple Limitations: only %d %s %r %% |
| * are supported. |
| * |
| * @param pattern a format string. |
| * @param arguments an array containing positional arguments. |
| * @return the formatted string. |
| */ |
| public static String format(String pattern, Object... arguments) { |
| return getPrinter().format(pattern, arguments).toString(); |
| } |
| |
| /** |
| * Perform Python-style string formatting, as per pattern % tuple Limitations: only %d %s %r %% |
| * are supported. |
| * |
| * @param pattern a format string. |
| * @param arguments a tuple containing positional arguments. |
| * @return the formatted string. |
| */ |
| public static String formatWithList(String pattern, List<?> arguments) { |
| return getPrinter().formatWithList(pattern, arguments).toString(); |
| } |
| |
| /** |
| * Perform Python-style string formatting, lazily. |
| * |
| * @param pattern a format string. |
| * @param arguments positional arguments. |
| * @return the formatted string. |
| */ |
| public static Formattable formattable(final String pattern, Object... arguments) { |
| final List<Object> args = Arrays.asList(arguments); |
| return new Formattable() { |
| @Override |
| public String toString() { |
| return formatWithList(pattern, args); |
| } |
| |
| @Override |
| public void formatTo(Formatter formatter, int flags, int width, int precision) { |
| Printer.getPrinter(formatter.out()).formatWithList(pattern, args); |
| } |
| }; |
| } |
| |
| /** |
| * Append a char to a buffer. In case of {@link IOException} throw an {@link AssertionError} |
| * instead |
| * |
| * @return buffer |
| */ |
| public static Appendable append(Appendable buffer, char c) { |
| try { |
| return buffer.append(c); |
| } catch (IOException e) { |
| throw new AssertionError(e); |
| } |
| } |
| |
| /** |
| * Append a char sequence to a buffer. In case of {@link IOException} throw an |
| * {@link AssertionError} instead |
| * |
| * @return buffer |
| */ |
| public static Appendable append(Appendable buffer, CharSequence s) { |
| try { |
| return buffer.append(s); |
| } catch (IOException e) { |
| throw new AssertionError(e); |
| } |
| } |
| |
| /** |
| * Append a char sequence range to a buffer. In case of {@link IOException} throw an |
| * {@link AssertionError} instead |
| * @return buffer |
| */ |
| private static Appendable append(Appendable buffer, CharSequence s, int start, int end) { |
| try { |
| return buffer.append(s, start, end); |
| } catch (IOException e) { |
| throw new AssertionError(e); |
| } |
| } |
| |
| /** Actual class that implements Printer API */ |
| public static class BasePrinter implements SkylarkPrinter { |
| // Methods of this class should not recurse through static methods of Printer |
| |
| protected final Appendable buffer; |
| |
| /** |
| * If true, the only percent sequences allowed in format strings are %s substitutions and %% |
| * escapes. |
| */ |
| protected final boolean simplifiedFormatStrings; |
| |
| /** |
| * Creates a printer. |
| * |
| * @param buffer the {@link Appendable} that will be written to |
| * @param simplifiedFormatStrings if true, format strings will allow only %s and %% |
| */ |
| protected BasePrinter(Appendable buffer, boolean simplifiedFormatStrings) { |
| this.buffer = buffer; |
| this.simplifiedFormatStrings = simplifiedFormatStrings; |
| } |
| |
| /** |
| * Creates a printer that writes to the given buffer and that does not use simplified format |
| * strings. |
| */ |
| protected BasePrinter(Appendable buffer) { |
| this(buffer, /*simplifiedFormatStrings=*/ false); |
| } |
| |
| /** |
| * Creates a printer that uses a fresh buffer and that does not use simplified format strings. |
| */ |
| protected BasePrinter() { |
| this(new StringBuilder()); |
| } |
| |
| @Override |
| public String toString() { |
| return buffer.toString(); |
| } |
| |
| /** |
| * Print an informal debug-only representation of object x. |
| * |
| * @param o the object |
| * @return the buffer, in fluent style |
| */ |
| public BasePrinter debugPrint(Object o) { |
| if (o instanceof SkylarkValue) { |
| ((SkylarkValue) o).debugPrint(this); |
| return this; |
| } |
| |
| return this.str(o); |
| } |
| |
| /** |
| * Print an informal representation of object x. Currently only differs from repr in the |
| * behavior for strings and labels at top-level, that are returned as is rather than quoted. |
| * |
| * @param o the object |
| * @return the buffer, in fluent style |
| */ |
| public BasePrinter str(Object o) { |
| if (o instanceof SkylarkValue) { |
| ((SkylarkValue) o).str(this); |
| return this; |
| } |
| |
| if (o instanceof String) { |
| return this.append((String) o); |
| } |
| return this.repr(o); |
| } |
| |
| /** |
| * Print an official representation of object x. For regular data structures, the value should |
| * be parsable back into an equal data structure. |
| * |
| * @param o the string a representation of which to repr. |
| * @return BasePrinter. |
| */ |
| @Override |
| public BasePrinter repr(Object o) { |
| if (o == null) { |
| // Java null is not a valid Skylark value, but sometimes printers are used on non-Skylark |
| // values such as Locations or ASTs. |
| this.append("null"); |
| |
| } else if (o instanceof SkylarkPrintable) { |
| ((SkylarkPrintable) o).repr(this); |
| |
| } else if (o instanceof String) { |
| writeString((String) o); |
| |
| } else if (o instanceof Integer || o instanceof Double) { |
| this.append(o.toString()); |
| |
| } else if (Boolean.TRUE.equals(o)) { |
| this.append("True"); |
| |
| } else if (Boolean.FALSE.equals(o)) { |
| this.append("False"); |
| |
| } else if (o instanceof Map<?, ?>) { |
| Map<?, ?> dict = (Map<?, ?>) o; |
| this.printList(dict.entrySet(), "{", ", ", "}", null); |
| |
| } else if (o instanceof List<?>) { |
| List<?> seq = (List<?>) o; |
| this.printList(seq, false); |
| |
| } else if (o instanceof Map.Entry<?, ?>) { |
| Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o; |
| this.repr(entry.getKey()); |
| this.append(": "); |
| this.repr(entry.getValue()); |
| } else if (o instanceof Class<?>) { |
| this.append(EvalUtils.getDataTypeNameFromClass((Class<?>) o)); |
| |
| } else if (o instanceof ASTNode || o instanceof Location) { |
| // AST node objects and locations are printed in tracebacks and error messages, |
| // it's safe to print their toString representations |
| this.append(o.toString()); |
| |
| } else { |
| // Other types of objects shouldn't be leaked to Skylark, but if happens, their |
| // .toString method shouldn't be used because their return values are likely to contain |
| // memory addresses or other nondeterministic information. |
| this.append("<unknown object " + o.getClass().getName() + ">"); |
| } |
| |
| return this; |
| } |
| |
| /** |
| * Write a properly escaped Skylark representation of a string to a buffer. |
| * |
| * @param s the string a representation of which to repr. |
| * @return this printer. |
| */ |
| protected BasePrinter writeString(String s) { |
| this.append(SKYLARK_QUOTATION_MARK); |
| int len = s.length(); |
| for (int i = 0; i < len; i++) { |
| char c = s.charAt(i); |
| escapeCharacter(c); |
| } |
| return this.append(SKYLARK_QUOTATION_MARK); |
| } |
| |
| private BasePrinter backslashChar(char c) { |
| return this.append('\\').append(c); |
| } |
| |
| private BasePrinter escapeCharacter(char c) { |
| if (c == SKYLARK_QUOTATION_MARK) { |
| return backslashChar(c); |
| } |
| switch (c) { |
| case '\\': |
| return backslashChar('\\'); |
| case '\r': |
| return backslashChar('r'); |
| case '\n': |
| return backslashChar('n'); |
| case '\t': |
| return backslashChar('t'); |
| default: |
| if (c < 32) { |
| //TODO(bazel-team): support \x escapes |
| return this.append(String.format("\\x%02x", (int) c)); |
| } |
| return this.append(c); // no need to support UTF-8 |
| } // endswitch |
| } |
| |
| /** |
| * Print a list of object representations |
| * |
| * @param list the list of objects to repr (each as with repr) |
| * @param before a string to print before the list items, e.g. an opening bracket |
| * @param separator a separator to print between items |
| * @param after a string to print after the list items, e.g. a closing bracket |
| * @param singletonTerminator null or a string to print after the list if it is a singleton The |
| * singleton case is notably relied upon in python syntax to distinguish a tuple of size one |
| * such as ("foo",) from a merely parenthesized object such as ("foo"). |
| * @return this printer. |
| */ |
| @Override |
| public BasePrinter printList( |
| Iterable<?> list, |
| String before, |
| String separator, |
| String after, |
| @Nullable String singletonTerminator) { |
| |
| this.append(before); |
| int len = appendListElements(list, separator); |
| if (singletonTerminator != null && len == 1) { |
| this.append(singletonTerminator); |
| } |
| return this.append(after); |
| } |
| |
| /** |
| * Appends the given elements to the specified {@link Appendable} and returns the number of |
| * elements. |
| */ |
| private int appendListElements(Iterable<?> list, String separator) { |
| boolean printSeparator = false; // don't print the separator before the first element |
| int len = 0; |
| for (Object o : list) { |
| if (printSeparator) { |
| this.append(separator); |
| } |
| this.repr(o); |
| printSeparator = true; |
| len++; |
| } |
| return len; |
| } |
| |
| /** |
| * Print a Skylark list or tuple of object representations |
| * |
| * @param list the contents of the list or tuple |
| * @param isTuple if true the list will be formatted with parentheses and with a trailing comma |
| * in case of one-element tuples. 'Soft' means that this limit may be exceeded because of |
| * formatting. |
| * @return this printer. |
| */ |
| @Override |
| public BasePrinter printList(Iterable<?> list, boolean isTuple) { |
| if (isTuple) { |
| return this.printList(list, "(", ", ", ")", ","); |
| } else { |
| return this.printList(list, "[", ", ", "]", null); |
| } |
| } |
| |
| /** |
| * Perform Python-style string formatting, similar to the {@code pattern % tuple} syntax. |
| * |
| * <p>The only supported placeholder patterns are |
| * <ul> |
| * <li>{@code %s} (convert as if by {@code str()}) |
| * <li>{@code %r} (convert as if by {@code repr()}) |
| * <li>{@code %d} (convert an integer to its decimal representation) |
| * </ul> |
| * To encode a literal percent character, escape it as {@code %%}. It is an error to have a |
| * non-escaped {@code %} at the end of the string or followed by any character not listed above. |
| * |
| * <p>If this printer has {@code simplifiedFormatStrings} set, only {@code %s} and {@code %%} |
| * are permitted. |
| * |
| * @param pattern a format string that may contain placeholders |
| * @param arguments an array containing arguments to substitute into the placeholders in order |
| * @return the formatted string |
| * @throws IllegalFormatException if {@code pattern} is not a valid format string, or if |
| * {@code arguments} mismatches the number or type of placeholders in {@code pattern} |
| */ |
| @Override |
| public BasePrinter format(String pattern, Object... arguments) { |
| return this.formatWithList(pattern, Arrays.asList(arguments)); |
| } |
| |
| /** |
| * Perform Python-style string formatting, similar to the {@code pattern % tuple} syntax. |
| * |
| * <p>Same as {@link #format(String, Object...)}, but with a list instead of variadic args. |
| */ |
| @Override |
| public BasePrinter formatWithList(String pattern, List<?> arguments) { |
| // TODO(bazel-team): support formatting arguments, and more complex Python patterns. |
| // N.B. MissingFormatWidthException is the only kind of IllegalFormatException |
| // whose constructor can take and display arbitrary error message, hence its use below. |
| |
| int length = pattern.length(); |
| int argLength = arguments.size(); |
| int i = 0; // index of next character in pattern |
| int a = 0; // index of next argument in arguments |
| |
| while (i < length) { |
| int p = pattern.indexOf('%', i); |
| if (p == -1) { |
| Printer.append(buffer, pattern, i, length); |
| break; |
| } |
| if (p > i) { |
| Printer.append(buffer, pattern, i, p); |
| } |
| if (p == length - 1) { |
| throw new MissingFormatWidthException( |
| "incomplete format pattern ends with %: " + this.repr(pattern)); |
| } |
| char directive = pattern.charAt(p + 1); |
| i = p + 2; |
| switch (directive) { |
| case '%': |
| this.append('%'); |
| continue; |
| case 'd': |
| case 'r': |
| case 's': |
| if (simplifiedFormatStrings && (directive != 's')) { |
| throw new UnknownFormatConversionException( |
| "cannot use %" + directive + " substitution placeholder when " |
| + "simplifiedFormatStrings is set"); |
| } |
| if (a >= argLength) { |
| throw new MissingFormatWidthException( |
| "not enough arguments for format pattern " |
| + Printer.repr(pattern) |
| + ": " |
| + Printer.repr(Tuple.copyOf(arguments))); |
| } |
| Object argument = arguments.get(a++); |
| switch (directive) { |
| case 'd': |
| if (argument instanceof Integer) { |
| this.append(argument.toString()); |
| continue; |
| } else { |
| throw new MissingFormatWidthException( |
| "invalid argument " + Printer.repr(argument) + " for format pattern %d"); |
| } |
| case 'r': |
| this.repr(argument); |
| continue; |
| case 's': |
| this.str(argument); |
| continue; |
| } |
| // fall through |
| default: |
| throw new MissingFormatWidthException( |
| // The call to Printer.repr doesn't cause an infinite recursion because it's |
| // only used to format a string properly |
| String.format("unsupported format character \"%s\" at index %s in %s", |
| String.valueOf(directive), p + 1, Printer.repr(pattern))); |
| } |
| } |
| if (a < argLength) { |
| throw new MissingFormatWidthException( |
| "not all arguments converted during string formatting"); |
| } |
| return this; |
| } |
| |
| @Override |
| public BasePrinter append(char c) { |
| Printer.append(buffer, c); |
| return this; |
| } |
| |
| @Override |
| public BasePrinter append(CharSequence s) { |
| Printer.append(buffer, s); |
| return this; |
| } |
| |
| BasePrinter append(CharSequence sequence, int start, int end) { |
| return this.append(sequence.subSequence(start, end)); |
| } |
| } |
| |
| /** A printer that breaks lines between the entries of lists, with proper indenting. */ |
| public static class PrettyPrinter extends BasePrinter { |
| static final int BASE_INDENT = 4; |
| private int indent; |
| |
| protected PrettyPrinter(Appendable buffer) { |
| super(buffer); |
| indent = 0; |
| } |
| |
| @Override |
| public BasePrinter printList( |
| Iterable<?> list, |
| String before, |
| String untrimmedSeparator, |
| String after, |
| @Nullable String singletonTerminator) { |
| |
| // If the list is empty, do not split the presentation over |
| // several lines. |
| if (!list.iterator().hasNext()) { |
| this.append(before + after); |
| return this; |
| } |
| |
| String separator = untrimmedSeparator.trim(); |
| |
| this.append(before + "\n"); |
| indent += BASE_INDENT; |
| boolean printSeparator = false; // don't print the separator before the first element |
| int len = 0; |
| for (Object o : list) { |
| if (printSeparator) { |
| this.append(separator + "\n"); |
| } |
| this.append(Strings.repeat(" ", indent)); |
| this.repr(o); |
| printSeparator = true; |
| len++; |
| } |
| if (singletonTerminator != null && len == 1) { |
| this.append(singletonTerminator); |
| } |
| this.append("\n"); |
| indent -= BASE_INDENT; |
| this.append(Strings.repeat(" ", indent) + after); |
| return this; |
| } |
| } |
| |
| /** |
| * A pretty printer that represents values in a form usable in WORKSPACE files. |
| * |
| * <p>In WORKSPACE files, the Label constructor is not available. Fortunately, in all places where |
| * a label is needed, we can pass the canonical string associated with this label. |
| */ |
| public static class WorkspacePrettyPrinter extends PrettyPrinter { |
| |
| protected WorkspacePrettyPrinter(Appendable buffer) { |
| super(buffer); |
| } |
| |
| @Override |
| public BasePrinter repr(Object o) { |
| if (o instanceof Label) { |
| this.repr(((Label) o).getCanonicalForm()); |
| } else { |
| super.repr(o); |
| } |
| return this; |
| } |
| } |
| |
| /** A version of {@code BasePrinter} that is able to print abbreviated lists. */ |
| public static final class LengthLimitedPrinter extends BasePrinter { |
| |
| private static final ImmutableSet<Character> SPECIAL_CHARS = |
| ImmutableSet.of(',', ' ', '"', '\'', ':', '(', ')', '[', ']', '{', '}'); |
| |
| private static final Pattern ARGS_PATTERN = Pattern.compile("<\\d+ more arguments>"); |
| |
| // Limits can be set several times recursively and then unset the same amount of times. |
| // But in fact they should be set only the first time and unset only the last time. |
| // To achieve that we need to keep track of the recursion depth. |
| private int recursionDepth; |
| // Current limit of symbols to print in the limited mode (`ignoreLimit = false`). |
| private int limit; |
| private boolean ignoreLimit = true; |
| private boolean previouslyShortened; |
| |
| /** |
| * Print a list of object representations. |
| * |
| * <p>The length of the output will be limited when both {@code maxItemsToPrint} and {@code |
| * criticalItemsStringLength} have values greater than zero. |
| * |
| * @param list the list of objects to repr (each as with repr) |
| * @param before a string to print before the list |
| * @param separator a separator to print between each object |
| * @param after a string to print after the list |
| * @param singletonTerminator null or a string to print after the list if it is a singleton The |
| * singleton case is notably relied upon in python syntax to distinguish a tuple of size one |
| * such as ("foo",) from a merely parenthesized object such as ("foo"). |
| * @param maxItemsToPrint the maximum number of elements to be printed. |
| * @param criticalItemsStringLength a soft limit for the total string length of all arguments. |
| * 'Soft' means that this limit may be exceeded because of formatting. |
| * @return the BasePrinter. |
| */ |
| LengthLimitedPrinter printAbbreviatedList( |
| Iterable<?> list, |
| String before, |
| String separator, |
| String after, |
| @Nullable String singletonTerminator, |
| int maxItemsToPrint, |
| int criticalItemsStringLength) { |
| this.append(before); |
| int len = appendListElements(list, separator, maxItemsToPrint, criticalItemsStringLength); |
| if (singletonTerminator != null && len == 1) { |
| this.append(singletonTerminator); |
| } |
| return this.append(after); |
| } |
| |
| /** |
| * Print a Skylark list or tuple of object representations |
| * |
| * @param list the contents of the list or tuple |
| * @param isTuple if true the list will be formatted with parentheses and with a trailing comma |
| * in case of one-element tuples. |
| * @param maxItemsToPrint the maximum number of elements to be printed. |
| * @param criticalItemsStringLength a soft limit for the total string length of all arguments. |
| * 'Soft' means that this limit may be exceeded because of formatting. |
| * @return this printer. |
| */ |
| public LengthLimitedPrinter printAbbreviatedList( |
| Iterable<?> list, boolean isTuple, int maxItemsToPrint, int criticalItemsStringLength) { |
| if (isTuple) { |
| return this.printAbbreviatedList( |
| list, "(", ", ", ")", ",", maxItemsToPrint, criticalItemsStringLength); |
| } else { |
| return this.printAbbreviatedList( |
| list, "[", ", ", "]", null, maxItemsToPrint, criticalItemsStringLength); |
| } |
| } |
| |
| /** |
| * Tries to append the given elements to the specified {@link Appendable} until specific limits |
| * are reached. |
| * |
| * @return the number of appended elements. |
| */ |
| private int appendListElements( |
| Iterable<?> list, String separator, int maxItemsToPrint, int criticalItemsStringLength) { |
| boolean printSeparator = false; // don't print the separator before the first element |
| boolean skipArgs = false; |
| int items = Iterables.size(list); |
| int len = 0; |
| // We don't want to print "1 more arguments", hence we don't skip arguments if there is only |
| // one above the limit. |
| int itemsToPrint = (items - maxItemsToPrint == 1) ? items : maxItemsToPrint; |
| enforceLimit(criticalItemsStringLength); |
| for (Object o : list) { |
| // We don't want to print "1 more arguments", even if we hit the string limit. |
| if (len == itemsToPrint || (hasHitLimit() && len < items - 1)) { |
| skipArgs = true; |
| break; |
| } |
| if (printSeparator) { |
| this.append(separator); |
| } |
| this.repr(o); |
| printSeparator = true; |
| len++; |
| } |
| ignoreLimit(); |
| if (skipArgs) { |
| this.append(separator); |
| this.append(String.format("<%d more arguments>", items - len)); |
| } |
| return len; |
| } |
| |
| @Override |
| public LengthLimitedPrinter append(CharSequence csq) { |
| if (ignoreLimit || hasOnlySpecialChars(csq)) { |
| // Don't update limit. |
| Printer.append(buffer, csq); |
| previouslyShortened = false; |
| } else { |
| int length = csq.length(); |
| if (length <= limit) { |
| limit -= length; |
| Printer.append(buffer, csq); |
| } else { |
| Printer.append(buffer, csq, 0, limit); |
| // We don't want to append multiple ellipses. |
| if (!previouslyShortened) { |
| Printer.append(buffer, "..."); |
| } |
| appendTrailingSpecialChars(csq, limit); |
| previouslyShortened = true; |
| limit = 0; |
| } |
| } |
| return this; |
| } |
| |
| @Override |
| public LengthLimitedPrinter append(char c) { |
| // Use the local `append(sequence)` method so that limits can apply |
| return this.append(String.valueOf(c)); |
| } |
| |
| /** |
| * Appends any trailing "special characters" (e.g. brackets, quotation marks) in the given |
| * sequence to the output buffer, regardless of the limit. |
| * |
| * <p>For example, let's look at foo(['too long']). Without this method, the shortened result |
| * would be foo(['too...) instead of the prettier foo(['too...']). |
| * |
| * <p>If the input string was already shortened and contains "<x more arguments>", this part |
| * will also be appended. |
| */ |
| // TODO(bazel-team): Given an input list |
| // |
| // [1, 2, 3, [10, 20, 30, 40, 50, 60], 4, 5, 6] |
| // |
| // the inner list gets doubly mangled as |
| // |
| // [1, 2, 3, [10, 20, 30, 40, <2 more argu...<2 more arguments>], <3 more arguments>] |
| private LengthLimitedPrinter appendTrailingSpecialChars(CharSequence csq, int limit) { |
| int length = csq.length(); |
| Matcher matcher = ARGS_PATTERN.matcher(csq); |
| // We assume that everything following the "x more arguments" part has to be copied, too. |
| int start = matcher.find() ? matcher.start() : length; |
| // Find the left-most non-arg char that has to be copied. |
| for (int i = start - 1; i > limit; --i) { |
| if (isSpecialChar(csq.charAt(i))) { |
| start = i; |
| } else { |
| break; |
| } |
| } |
| if (start < length) { |
| Printer.append(buffer, csq, start, csq.length()); |
| } |
| return this; |
| } |
| |
| /** |
| * Returns whether the given sequence denotes characters that are not part of the value of an |
| * argument. |
| * |
| * <p>Examples are brackets, braces and quotation marks. |
| */ |
| private boolean hasOnlySpecialChars(CharSequence csq) { |
| for (int i = 0; i < csq.length(); ++i) { |
| if (!isSpecialChar(csq.charAt(i))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private boolean isSpecialChar(char c) { |
| return SPECIAL_CHARS.contains(c); |
| } |
| |
| boolean hasHitLimit() { |
| return limit <= 0; |
| } |
| |
| private void enforceLimit(int limit) { |
| ignoreLimit = false; |
| if (recursionDepth == 0) { |
| this.limit = limit; |
| ++recursionDepth; |
| } |
| } |
| |
| private void ignoreLimit() { |
| if (recursionDepth > 0) { |
| --recursionDepth; |
| } |
| if (recursionDepth == 0) { |
| ignoreLimit = true; |
| } |
| } |
| } |
| } |