diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/syntax/Printer.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/syntax/Printer.java | 264 |
1 files changed, 254 insertions, 10 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Printer.java b/src/main/java/com/google/devtools/build/lib/syntax/Printer.java index 0fa29641fe..26e6fdabd6 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/Printer.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/Printer.java @@ -14,6 +14,8 @@ package com.google.devtools.build.lib.syntax; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.devtools.build.lib.syntax.SkylarkList.Tuple; import com.google.devtools.build.lib.vfs.PathFragment; @@ -27,6 +29,8 @@ import java.util.MissingFormatWidthException; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * (Pretty) Printing of Skylark values @@ -35,6 +39,19 @@ public final class Printer { private static final char SKYLARK_QUOTATION_MARK = '"'; + /* + * Suggested maximum number of list elements that should be printed via printList(). + * By default, this setting is not considered and no limitation takes place. + */ + static final int SUGGESTED_CRITICAL_LIST_ELEMENTS_COUNT = 4; + + /* + * Suggested limit for printList() 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. + */ + static final int SUGGESTED_CRITICAL_LIST_ELEMENTS_STRING_LENGTH = 32; + private Printer() { } @@ -257,21 +274,108 @@ public final class Printer { String after, String singletonTerminator, char quotationMark) { + return printList( + buffer, list, before, separator, after, singletonTerminator, quotationMark, -1, -1); + } + + /** + * 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 buffer an appendable buffer onto which to write the list. + * @param list the list of objects to write (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 quotationMark The quotation mark to be used (' or ") + * @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 Appendable, in fluent style. + */ + public static Appendable printList(Appendable buffer, Iterable<?> list, String before, + String separator, String after, String singletonTerminator, char quotationMark, + int maxItemsToPrint, int criticalItemsStringLength) { + append(buffer, before); + int len = 0; + // Limits the total length of the string representation of the elements, if specified. + if (maxItemsToPrint > 0 && criticalItemsStringLength > 0) { + len = appendListElements(LengthLimitedAppendable.create(buffer, criticalItemsStringLength), + list, separator, quotationMark, maxItemsToPrint); + } else { + len = appendListElements(buffer, list, separator, quotationMark); + } + if (singletonTerminator != null && len == 1) { + append(buffer, singletonTerminator); + } + return append(buffer, after); + } + + public static Appendable printList(Appendable buffer, Iterable<?> list, String before, + String separator, String after, String singletonTerminator, int maxItemsToPrint, + int criticalItemsStringLength) { + return printList(buffer, list, before, separator, after, singletonTerminator, + SKYLARK_QUOTATION_MARK, maxItemsToPrint, criticalItemsStringLength); + } + + /** + * Appends the given elements to the specified {@link Appendable} and returns the number of + * elements. + */ + private static int appendListElements( + Appendable appendable, Iterable<?> list, String separator, char quotationMark) { boolean printSeparator = false; // don't print the separator before the first element int len = 0; - append(buffer, before); for (Object o : list) { if (printSeparator) { - append(buffer, separator); + append(appendable, separator); } - write(buffer, o, quotationMark); + write(appendable, o, quotationMark); printSeparator = true; len++; } - if (singletonTerminator != null && len == 1) { - append(buffer, singletonTerminator); + return len; + } + + /** + * Tries to append the given elements to the specified {@link Appendable} until specific limits + * are reached. + * @return the number of appended elements. + */ + private static int appendListElements(LengthLimitedAppendable appendable, Iterable<?> list, + String separator, char quotationMark, int maxItemsToPrint) { + 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; + appendable.enforceLimit(); + for (Object o : list) { + // We don't want to print "1 more arguments", even if we hit the string limit. + if (len == itemsToPrint || (appendable.hasHitLimit() && len < items - 1)) { + skipArgs = true; + break; + } + if (printSeparator) { + append(appendable, separator); + } + write(appendable, o, quotationMark); + printSeparator = true; + len++; } - return append(buffer, after); + appendable.ignoreLimit(); + if (skipArgs) { + append(appendable, separator); + append(appendable, String.format("<%d more arguments>", items - len)); + } + return len; } public static Appendable printList(Appendable buffer, Iterable<?> list, String before, @@ -286,17 +390,27 @@ public final class Printer { * @param list the contents of the list or tuple * @param isTuple is it a tuple or a list? * @param quotationMark The quotation mark to be used (' or ") + * @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 Appendable, in fluent style. */ - public static Appendable printList( - Appendable buffer, Iterable<?> list, boolean isTuple, char quotationMark) { + public static Appendable printList(Appendable buffer, Iterable<?> list, boolean isTuple, + char quotationMark, int maxItemsToPrint, int criticalItemsStringLength) { if (isTuple) { - return printList(buffer, list, "(", ", ", ")", ",", quotationMark); + return printList(buffer, list, "(", ", ", ")", ",", quotationMark, maxItemsToPrint, + criticalItemsStringLength); } else { - return printList(buffer, list, "[", ", ", "]", null, quotationMark); + return printList(buffer, list, "[", ", ", "]", null, quotationMark, maxItemsToPrint, + criticalItemsStringLength); } } + public static Appendable printList( + Appendable buffer, Iterable<?> list, boolean isTuple, char quotationMark) { + return printList(buffer, list, isTuple, quotationMark, -1, -1); + } + /** * Print a list of object representations * @param list the list of objects to write (each as with repr) @@ -443,4 +557,134 @@ public final class Printer { } return buffer; } + + /** + * Helper class for {@code Appendable}s that want to limit the length of their input. + * + * <p>Instances of this class act as a proxy for one {@code Appendable} object and decide whether + * the given input (or parts of it) can be written to the underlying {@code Appendable}, depending + * on whether the specified maximum length has been met or not. + */ + private static final class LengthLimitedAppendable implements Appendable { + + private static final ImmutableSet<Character> SPECIAL_CHARS = + ImmutableSet.of(',', ' ', '"', '\'', ':', '(', ')', '[', ']', '{', '}'); + + private static final Pattern ARGS_PATTERN = Pattern.compile("<\\d+ more arguments>"); + + private final Appendable original; + private int limit; + private boolean ignoreLimit; + private boolean previouslyShortened; + + private LengthLimitedAppendable(Appendable original, int limit) { + this.original = original; + this.limit = limit; + } + + public static LengthLimitedAppendable create(Appendable original, int limit) { + // We don't want to overwrite the limit if original is already an instance of this class. + return (original instanceof LengthLimitedAppendable) + ? (LengthLimitedAppendable) original : new LengthLimitedAppendable(original, limit); + } + + @Override + public Appendable append(CharSequence csq) throws IOException { + if (ignoreLimit || hasOnlySpecialChars(csq)) { + // Don't update limit. + original.append(csq); + previouslyShortened = false; + } else { + int length = csq.length(); + if (length <= limit) { + limit -= length; + original.append(csq); + } else { + original.append(csq, 0, limit); + // We don't want to append multiple ellipses. + if (!previouslyShortened) { + original.append("..."); + } + appendTrailingSpecialChars(csq, limit); + previouslyShortened = true; + limit = 0; + } + } + return this; + } + + /** + * 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. + */ + private void appendTrailingSpecialChars(CharSequence csq, int limit) throws IOException { + 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) { + original.append(csq, start, csq.length()); + } + } + + /** + * 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); + } + + @Override + public Appendable append(CharSequence csq, int start, int end) throws IOException { + return append(csq.subSequence(start, end)); + } + + @Override + public Appendable append(char c) throws IOException { + return append(String.valueOf(c)); + } + + public boolean hasHitLimit() { + return limit <= 0; + } + + public void enforceLimit() { + ignoreLimit = false; + } + + public void ignoreLimit() { + ignoreLimit = true; + } + + @Override + public String toString() { + return original.toString(); + } + } } |