// 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.)
*
*
For example, the expression
* glob(['*.java']) + ['x.properties']
* 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 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 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.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.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 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 strings = Iterables.transform(list, Functions.toStringFunction());
return new GlobCriteria(ImmutableList.copyOf(strings), ImmutableList.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 include, ImmutableList exclude) {
return new GlobCriteria(include, exclude, true);
}
/**
* Constructs a glob call with include and exclude list.
*/
private GlobCriteria(ImmutableList include, ImmutableList 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 getIncludePatterns() {
return include;
}
/**
* Returns the patterns that were excluded in this {@code glob()} call.
*/
public ImmutableList 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, glob(['abc', 'def'], exclude=['uvw', 'xyz'])
* or ['foo', 'bar', 'baz']
.
*/
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 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 parseList(@Nullable String text) {
if (text == null) {
return ImmutableList.of();
}
Iterable split = Splitter.on(", ").split(text);
Builder 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();
}
}