| // Copyright 2014 Google Inc. 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.Preconditions; |
| 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 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(); |
| } |
| } |