Skylark: Add lstrip and rstrip functions.

--
MOS_MIGRATED_REVID=105498175
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
index 9f1e80f..7858593 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
@@ -15,6 +15,7 @@
 package com.google.devtools.build.lib.syntax;
 
 import com.google.common.base.Joiner;
+import com.google.common.base.CharMatcher;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
@@ -141,6 +142,113 @@
     }
   };
 
+  private static String stringLStrip(String self, String chars) {
+    CharMatcher matcher = CharMatcher.anyOf(chars);
+    for (int i = 0; i < self.length(); i++) {
+      if (!matcher.matches(self.charAt(i))) {
+        return self.substring(i);
+      }
+    }
+    return ""; // All characters were stripped.
+  }
+
+  private static String stringRStrip(String self, String chars) {
+    CharMatcher matcher = CharMatcher.anyOf(chars);
+    for (int i = self.length() - 1; i >= 0; i--) {
+      if (!matcher.matches(self.charAt(i))) {
+        return self.substring(0, i + 1);
+      }
+    }
+    return ""; // All characters were stripped.
+  }
+
+  @SkylarkSignature(
+    name = "lstrip",
+    objectType = StringModule.class,
+    returnType = String.class,
+    doc =
+        "Returns a copy of the string where leading characters that appear in <code>chars</code>"
+            + "are removed."
+            + "<pre class=\"language-python\">"
+            + "\"abcba\".lstrip(\"ba\") == \"cba\""
+            + "</pre",
+    mandatoryPositionals = {
+      @Param(name = "self", type = String.class, doc = "This string"),
+    },
+    optionalPositionals = {
+      @Param(
+        name = "chars",
+        type = String.class,
+        doc = "The characters to remove",
+        defaultValue = "' \t'"
+      )
+    }
+  )
+  private static BuiltinFunction lstrip =
+      new BuiltinFunction("lstrip") {
+        public String invoke(String self, String chars) {
+          return stringLStrip(self, chars);
+        }
+      };
+
+  @SkylarkSignature(
+    name = "rstrip",
+    objectType = StringModule.class,
+    returnType = String.class,
+    doc =
+        "Returns a copy of the string where trailing characters that appear in <code>chars</code>"
+            + "are removed."
+            + "<pre class=\"language-python\">"
+            + "\"abcba\".rstrip(\"ba\") == \"abc\""
+            + "</pre",
+    mandatoryPositionals = {
+      @Param(name = "self", type = String.class, doc = "This string"),
+    },
+    optionalPositionals = {
+      @Param(
+        name = "chars",
+        type = String.class,
+        doc = "The characters to remove",
+        defaultValue = "' \t'"
+      )
+    }
+  )
+  private static BuiltinFunction rstrip =
+      new BuiltinFunction("rstrip") {
+        public String invoke(String self, String chars) {
+          return stringRStrip(self, chars);
+        }
+      };
+
+  @SkylarkSignature(
+    name = "strip",
+    objectType = StringModule.class,
+    returnType = String.class,
+    doc =
+        "Returns a copy of the string where trailing characters that appear in <code>chars</code>"
+            + "are removed."
+            + "<pre class=\"language-python\">"
+            + "\"abcba\".strip(\"ba\") == \"abc\""
+            + "</pre",
+    mandatoryPositionals = {
+      @Param(name = "self", type = String.class, doc = "This string"),
+    },
+    optionalPositionals = {
+      @Param(
+        name = "chars",
+        type = String.class,
+        doc = "The characters to remove",
+        defaultValue = "' \t'"
+      )
+    }
+  )
+  private static BuiltinFunction strip =
+      new BuiltinFunction("strip") {
+        public String invoke(String self, String chars) {
+          return stringLStrip(stringRStrip(self, chars), chars);
+        }
+      };
+
   @SkylarkSignature(name = "replace", objectType = StringModule.class, returnType = String.class,
       doc = "Returns a copy of the string in which the occurrences "
           + "of <code>old</code> have been replaced with <code>new</code>, optionally restricting "
@@ -667,18 +775,6 @@
     }
   };
 
-  // TODO(bazel-team): Maybe support an argument to tell the type of the whitespace.
-  @SkylarkSignature(name = "strip", objectType = StringModule.class, returnType = String.class,
-      doc = "Returns a copy of the string in which all whitespace characters "
-          + "have been stripped from the beginning and the end of the string.",
-      mandatoryPositionals = {
-        @Param(name = "self", type = String.class, doc = "This string.")})
-  private static BuiltinFunction strip = new BuiltinFunction("strip") {
-    public String invoke(String self) {
-      return self.trim();
-    }
-  };
-
   // slice operator
   @SkylarkSignature(name = "$slice", objectType = String.class,
       documented = false,
@@ -954,7 +1050,7 @@
 
   @SkylarkSignature(name = "values", objectType = Map.class,
       returnType = HackHackEitherList.class,
-      doc = "Return the list of values. Dictionaries are always sorted by their keys:"
+      doc = "Returns the list of values. Dictionaries are always sorted by their keys:"
           + "<pre class=\"language-python\">"
           + "{2: \"a\", 4: \"b\", 1: \"c\"}.values() == [\"c\", \"a\", \"b\"]</pre>\n",
       mandatoryPositionals = {@Param(name = "self", type = Map.class, doc = "This dict.")},
@@ -970,7 +1066,7 @@
 
   @SkylarkSignature(name = "items", objectType = Map.class,
       returnType = HackHackEitherList.class,
-      doc = "Return the list of key-value tuples. Dictionaries are always sorted by their keys:"
+      doc = "Returns the list of key-value tuples. Dictionaries are always sorted by their keys:"
           + "<pre class=\"language-python\">"
           + "{2: \"a\", 4: \"b\", 1: \"c\"}.items() == [(1, \"c\"), (2, \"a\"), (4, \"b\")]"
           + "</pre>\n",
@@ -993,7 +1089,7 @@
 
   @SkylarkSignature(name = "keys", objectType = Map.class,
       returnType = HackHackEitherList.class,
-      doc = "Return the list of keys. Dictionaries are always sorted by their keys:"
+      doc = "Returns the list of keys. Dictionaries are always sorted by their keys:"
           + "<pre class=\"language-python\">{2: \"a\", 4: \"b\", 1: \"c\"}.keys() == [1, 2, 4]"
           + "</pre>\n",
       mandatoryPositionals = {
@@ -1009,7 +1105,7 @@
   };
 
   @SkylarkSignature(name = "get", objectType = Map.class,
-      doc = "Return the value for <code>key</code> if <code>key</code> is in the dictionary, "
+      doc = "Returns the value for <code>key</code> if <code>key</code> is in the dictionary, "
           + "else <code>default</code>. If <code>default</code> is not given, it defaults to "
           + "<code>None</code>, so that this method never throws an error.",
       mandatoryPositionals = {
@@ -1253,7 +1349,7 @@
   };
 
   @SkylarkSignature(name = "enumerate", returnType = HackHackEitherList.class,
-      doc = "Return a list of pairs (two-element tuples), with the index (int) and the item from"
+      doc = "Returns a list of pairs (two-element tuples), with the index (int) and the item from"
           + " the input list.\n<pre class=\"language-python\">"
           + "enumerate([24, 21, 84]) == [(0, 24), (1, 21), (2, 84)]</pre>\n",
       mandatoryPositionals = {
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
index de92681..7c0828b 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
@@ -1,4 +1,4 @@
-// Copyright 2006 Google Inc. All Rights Reserved.
+// Copyright 2006 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.
@@ -977,13 +977,6 @@
   }
 
   @Test
-  public void testStrip() throws Exception {
-    new BothModesTest()
-        .testStatement("' abc\t'.strip()", "abc")
-        .testStatement("'abc  '.strip()", "abc");
-  }
-
-  @Test
   public void testBool() throws Exception {
     new BothModesTest()
         .testStatement("bool(1)", Boolean.TRUE)
@@ -1097,4 +1090,33 @@
         .testStatement("'A'.isalpha()", true)
         .testStatement("'AbZ'.isalpha()", true);
   }
+
+  @Test
+  public void testLStrip() throws Exception {
+    new BothModesTest()
+        .testStatement("'abc'.lstrip('')", "abc")
+        .testStatement("'abcba'.lstrip('ba')", "cba")
+        .testStatement("'abc'.lstrip('xyz')", "abc")
+        .testStatement("'  abc  '.lstrip()", "abc  ");
+  }
+
+  @Test
+  public void testRStrip() throws Exception {
+    new BothModesTest()
+        .testStatement("'abc'.rstrip('')", "abc")
+        .testStatement("'abcba'.rstrip('ba')", "abc")
+        .testStatement("'abc'.rstrip('xyz')", "abc")
+        .testStatement("'  abc  '.rstrip()", "  abc");
+  }
+
+  @Test
+  public void testStrip() throws Exception {
+    new BothModesTest()
+        .testStatement("'abc'.strip('')", "abc")
+        .testStatement("'abcba'.strip('ba')", "c")
+        .testStatement("'abc'.strip('xyz')", "abc")
+        .testStatement("'  abc  '.strip()", "abc")
+        .testStatement("' abc\t'.strip()", "abc")
+        .testStatement("'abc'.strip('.')", "abc");
+  }
 }