// Copyright 2014 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.Functions; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.util.Preconditions; import java.util.List; import javax.annotation.Nullable; /** * Either the arguments to a glob call (the include and exclude lists) or the * contents of a fixed list that was appended to a list of glob results. * (The latter need to be stored by {@link GlobList} in order to fully * reproduce the inputs that created the output list.) * * <p>For example, the expression * <code>glob(['*.java']) + ['x.properties']</code> * will result in two GlobCriteria: one has include = ['*.java'], glob = true * and the other, include = ['x.properties'], glob = false. */ public class GlobCriteria { /** * A list of names or patterns that are included by this glob. They should * consist of characters that are valid in labels in the BUILD language. */ private final ImmutableList<String> include; /** * A list of names or patterns that are excluded by this glob. They should * consist of characters that are valid in labels in the BUILD language. */ private final ImmutableList<String> exclude; /** True if the includes list was passed to glob(), false if not. */ private final boolean glob; /** * Parses criteria from its {@link #toExpression} form. * Package-private for use by tests and GlobList. * @throws IllegalArgumentException if the expression cannot be parsed */ public static GlobCriteria parse(String text) { if (text.startsWith("glob([") && text.endsWith("])")) { int excludeIndex = text.indexOf("], exclude=["); if (excludeIndex == -1) { String listText = text.substring(6, text.length() - 2); return new GlobCriteria(parseList(listText), ImmutableList.<String>of(), true); } else { String listText = text.substring(6, excludeIndex); String excludeText = text.substring(excludeIndex + 12, text.length() - 2); return new GlobCriteria(parseList(listText), parseList(excludeText), true); } } else if (text.startsWith("[") && text.endsWith("]")) { String listText = text.substring(1, text.length() - 1); return new GlobCriteria(parseList(listText), ImmutableList.<String>of(), false); } else { throw new IllegalArgumentException( "unrecognized format (not from toExpression?): " + text); } } /** * Constructs a copy of a given glob critera object, with additional exclude patterns added. * * @param base a glob criteria object to copy. Must be an actual glob * @param excludes a list of pattern strings indicating new excludes to provide * @return a new glob criteria object which contains the same parameters as {@code base}, with * the additional patterns in {@code excludes} added. * @throws IllegalArgumentException if {@code base} is not a glob */ public static GlobCriteria createWithAdditionalExcludes(GlobCriteria base, List<String> excludes) { Preconditions.checkArgument(base.isGlob()); return fromGlobCall(base.include, ImmutableList.copyOf(Iterables.concat(base.exclude, excludes))); } /** * Constructs a copy of a fixed list, converted to Strings. */ public static GlobCriteria fromList(Iterable<?> list) { Iterable<String> strings = Iterables.transform(list, Functions.toStringFunction()); return new GlobCriteria(ImmutableList.copyOf(strings), ImmutableList.<String>of(), false); } /** * Constructs a glob call with include and exclude list. * * @param include list of included patterns * @param exclude list of excluded patterns */ public static GlobCriteria fromGlobCall( ImmutableList<String> include, ImmutableList<String> exclude) { return new GlobCriteria(include, exclude, true); } /** * Constructs a glob call with include and exclude list. */ private GlobCriteria(ImmutableList<String> include, ImmutableList<String> exclude, boolean glob) { this.include = include; this.exclude = exclude; this.glob = glob; } /** * Returns the patterns that were included in this {@code glob()} call. */ public ImmutableList<String> getIncludePatterns() { return include; } /** * Returns the patterns that were excluded in this {@code glob()} call. */ public ImmutableList<String> getExcludePatterns() { return exclude; } /** * Returns true if the include list was passed to {@code glob()}, false * if it was a fixed list. If this returns false, the exclude list will * always be empty. */ public boolean isGlob() { return glob; } /** * Returns a String that represents this glob as a BUILD expression. * For example, <code>glob(['abc', 'def'], exclude=['uvw', 'xyz'])</code> * or <code>['foo', 'bar', 'baz']</code>. */ public String toExpression() { StringBuilder sb = new StringBuilder(); if (glob) { sb.append("glob("); } sb.append('['); appendList(sb, include); if (!exclude.isEmpty()) { sb.append("], exclude=["); appendList(sb, exclude); } sb.append(']'); if (glob) { sb.append(')'); } return sb.toString(); } @Override public String toString() { return toExpression(); } /** * Takes a list of Strings, quotes them in single quotes, and appends them to * a StringBuilder separated by a comma and space. This can be parsed back * out by {@link #parseList}. */ private static void appendList(StringBuilder sb, List<String> list) { boolean first = true; for (String content : list) { if (!first) { sb.append(", "); } sb.append('\'').append(content).append('\''); first = false; } } /** * Takes a String in the format created by {@link #appendList} and returns * the original Strings. A null String (which may be returned when Pattern * does not find a match) or the String "" (which will be captured in "[]") * will result in an empty list. */ private static ImmutableList<String> parseList(@Nullable String text) { if (text == null) { return ImmutableList.of(); } Iterable<String> split = Splitter.on(", ").split(text); Builder<String> listBuilder = ImmutableList.builder(); for (String element : split) { if (!element.isEmpty()) { if ((element.length() < 2) || !element.startsWith("'") || !element.endsWith("'")) { throw new IllegalArgumentException("expected a filename or pattern in quotes: " + text); } listBuilder.add(element.substring(1, element.length() - 1)); } } return listBuilder.build(); } }