Index: tests/test_Base.js
===================================================================
--- tests/test_Base.js	(revision 1074)
+++ tests/test_Base.js	(working copy)
@@ -473,4 +473,117 @@
     t.is( median([3,1,2]), 2, 'three arguments (array)');
     t.is( median(3,1,2,4), 2.5, 'four arguments (arg list)');
     t.is( median([3,1,2,4]), 2.5, 'four arguments (array)');
+    
+    /* startsWith */
+    try {
+        startsWith();
+        t.ok( false, "no arguments didn't raise!" );
+    } catch (e) {
+        t.is( e.name, 'TypeError', "no arguments raised correctly" );
+    }
+    try {
+        startsWith('hello');
+        t.ok( false, "one argument didn't raise!" );
+    } catch (e) {
+        t.is( e.name, 'TypeError', "one argument raised correctly" );
+    }
+    t.is( startsWith('hello', 'he'), true, 'first two chars');
+    t.is( startsWith('hello', 'hello'), true, 'equal strings');
+    t.is( startsWith('hello', 'hello world'), false, 'prefix is longer');
+    t.is( startsWith('hello', ''), true, 'empty string');
+    t.is( startsWith('hello', 'ello'), false, 'all chars but the first');
+    t.is( startsWith('hello', 'ello', 1), true, 'all char but the first (start = 1)');
+    t.is( startsWith('hello', 'o', 4), true, 'char of index 4 (start = 4)');
+    t.is( startsWith('hello', 'o', 5), false, 'char of index 4 (start = 5)');
+    t.is( startsWith('hello', '', 5), true, 'empty string (start = 5)');
+    t.is( startsWith('hello', 'lo', 6), false, 'last two chars (start = 6)');
+    t.is( startsWith('helloworld', 'lowo', 3), true, 'chars between index 3 and 6 (start = 3)');
+    t.is( startsWith('helloworld', 'lowo', 3, 7), true, 'chars between index 3 and 6 (start = 3, end = 7)');
+    t.is( startsWith('helloworld', 'lowo', 3, 6), false, 'chars between index 3 and 6 (start = 3, end = 6)');
+    // test negative indices
+    t.is( startsWith('hello', 'he', 0, -1), true, 'first two chars (start = 0, end = -1)');
+    t.is( startsWith('hello', 'he', -53, -1), true, 'first two chars (start = -53, end = -1)');
+    t.is( startsWith('hello', 'hello', 0, -1), false, 'equal strings (start = 0, end = -1)');
+    t.is( startsWith('hello', 'hello world', -1, -10), false, 'prefix is longer (start = 1, end = -10)');
+    t.is( startsWith('hello', 'ello', -5), false, 'all chars but the first (start = -5)');
+    t.is( startsWith('hello', 'ello', -4), true, 'all chars but the first (start = -4)');
+    t.is( startsWith('hello', 'o', -2), false, 'last char (start = -2)');
+    t.is( startsWith('hello', 'o', -1), true, 'last char (start = -1)');
+    t.is( startsWith('hello', '', -3, -3), true, 'empty string (start = -3, end = -3)');
+    t.is( startsWith('hello', 'lo', -9), false, 'last two chars (start = -9)');
+    // non strings return false
+    t.is( startsWith('hello', new Date()), false, 'with Date object');
+    t.is( startsWith('hello', 42), false, 'with number');
+    // test array arguments
+    t.is( startsWith('hello', ['he', 'ha']), true, "with 'hello', ['he', 'ha']");
+    t.is( startsWith('hello', ['lo', 'llo']), false, "with 'hello', ['lo', 'llo']");
+    t.is( startsWith('hello', ['hellox', 'hello']), true, "with 'hello', ['hellox', 'hello']");
+    t.is( startsWith('hello', []), false, 'empty array');
+    t.is( startsWith('helloworld', ['hellowo', 'rld', 'lowo'], 3), true, "with 'helloworld', ['hellowo', 'rld', 'lowo'] (start = 3)");
+    t.is( startsWith('helloworld', ['hellowo', 'ello', 'rld'], 3), false, "with 'helloworld', ['hellowo', 'ello', 'rld'] (start = 3)");
+    t.is( startsWith('hello', ['lo', 'he'], 0, -1), true, "with 'hello', ['lo', 'he'] (start = 0, end = -1)");
+    t.is( startsWith('hello', ['he', 'hel'], 0, 0), false, "with 'hello', ['he', 'hel'] (start = 0, end = 0)");
+    t.is( startsWith('hello', ['he', 'hel'], 0, 2), true, "with 'hello', ['he', 'hel'] (start = 0, end = 0)");
+    // array with non strings return false
+    t.is( startsWith('hello', [new Date()]), false, 'with Date object');
+    t.is( startsWith('hello', [42]), false, 'with number');
+    
+    /* endsWith */
+    try {
+        endsWith();
+        t.ok( false, "no arguments didn't raise!" );
+    } catch (e) {
+        t.is( e.name, 'TypeError', "no arguments raised correctly" );
+    }
+    try {
+        endsWith('hello');
+        t.ok( false, "one argument didn't raise!" );
+    } catch (e) {
+        t.is( e.name, 'TypeError', "one argument raised correctly" );
+    }
+    t.is( endsWith('hello', 'lo'), true, 'last two chars');
+    t.is( endsWith('hello', 'hello'), true, 'equal strings');
+    t.is( endsWith('hello', 'he'), false, 'first two chars');
+    t.is( endsWith('hello', ''), true, 'empty string');
+    t.is( endsWith('hello', 'hello world'), false, 'second string is longer');
+    t.is( endsWith('helloworld', 'worl'), false, 'last five chars but one');
+    t.is( endsWith('helloworld', 'worl', 3, 9), true, 'last five chars but one (start = 3, end = 9)');
+    t.is( endsWith('helloworld', 'world', 3, 12), true, 'last five chars (start = 3, end = 12)');
+    t.is( endsWith('helloworld', 'lowo', 1, 7), true, 'chars between index 3 and 6 (start = 1, end = 7)');
+    t.is( endsWith('helloworld', 'lowo', 2, 7), true, 'chars between index 3 and 6 (start = 2, end = 7)');
+    t.is( endsWith('helloworld', 'lowo', 3, 7), true, 'chars between index 3 and 6 (start = 3, end = 7)');
+    t.is( endsWith('helloworld', 'lowo', 4, 7), false, 'chars between index 3 and 6 (start = 4, end = 7)');
+    t.is( endsWith('helloworld', 'lowo', 3, 8), false, 'chars between index 3 and 6 (start = 3, end = 8)');
+    t.is( endsWith('ab', 'ab', 0, 1), false, 'same string (start = 0, end = 1)');
+    t.is( endsWith('ab', 'ab', 0, 0), false, 'same string (start = 0, end = 0)');
+    // test negative indices
+    t.is( endsWith('hello', 'lo', -2), true, 'last two chars (start = -2)');
+    t.is( endsWith('hello', 'he', -2), false, 'first two chars (start = -2)');
+    t.is( endsWith('hello', '', -3, -3), true, 'empty string (start = -3, end = -3)');
+    t.is( endsWith('hello', 'hello world', -10, -2), false, 'suffix is longer (start = -10, end = -2)');
+    t.is( endsWith('helloworld', 'worl', -6), false, 'last five chars but one (start = -6)');
+    t.is( endsWith('helloworld', 'worl', -5, -1), true, 'last five chars but one (start = -5, end = -1)');
+    t.is( endsWith('helloworld', 'worl', -5, 9), true, 'last five chars but one (start = -5, end = 9)');
+    t.is( endsWith('helloworld', 'world', -7, 12), true, 'last five chars (start = -7, end = 12)');
+    t.is( endsWith('helloworld', 'lowo', -99, -3), true, 'empty string (start = 99, end = -3)');
+    t.is( endsWith('helloworld', 'lowo', -8, -3), true, 'chars between index 3 and 6 (start = -8, end = -3)');
+    t.is( endsWith('helloworld', 'lowo', -7, -3), true, 'chars between index 3 and 6 (start = -7, end = -3)');
+    t.is( endsWith('helloworld', 'lowo', -3, -4), false, 'chars between index 3 and 6 (start = -3, end = -4)');
+    t.is( endsWith('helloworld', 'lowo', -8, -2), false, 'chars between index 3 and 6 (start = -8, end = -2)');
+    // non strings return false
+    t.is( endsWith('hello', new Date()), false, 'with Date object');
+    t.is( endsWith('hello', 42), false, 'with number');
+    // test array arguments
+    t.is( endsWith('hello', ['he', 'ha']), false, "with 'hello', ['he', 'ha']");
+    t.is( endsWith('hello', ['lo', 'llo']), true, "with 'hello', ['lo', 'llo']");
+    t.is( endsWith('hello', ['hellox', 'hello']), true, "with 'hello', ['hellox', 'hello']");
+    t.is( endsWith('hello', []), false, 'empty array');
+    t.is( endsWith('helloworld', ['hellowo', 'rld', 'lowo'], 3), true, "with 'helloworld', ['hellowo', 'rld', 'lowo'] (start = 3)");
+    t.is( endsWith('helloworld', ['hellowo', 'ello', 'rld'], 3, -1), false, "with 'helloworld', ['hellowo', 'ello', 'rld'] (start = 3, end = -1)");
+    t.is( endsWith('hello', ['hell', 'ell'], 0, -1), true, "with 'hello', ['lo', 'he'] (start = 0, end = -1)");
+    t.is( endsWith('hello', ['he', 'hel'], 0, 1), false, "with 'hello', ['he', 'hel'] (start = 0, end = 1)");
+    t.is( endsWith('hello', ['he', 'hell'], 0, 4), true, "with 'hello', ['he', 'hel'] (start = 0, end = 4)");
+    // array with non strings return false
+    t.is( endsWith('hello', [new Date()]), false, 'with Date object');
+    t.is( endsWith('hello', [42]), false, 'with number');
 };
Index: MochiKit/Base.js
===================================================================
--- MochiKit/Base.js	(revision 1074)
+++ MochiKit/Base.js	(working copy)
@@ -1131,6 +1131,106 @@
             }
         }
         return o;
+    },
+    
+    _adjustIndices: function (start, end, len) {
+    	if (end > len) {
+    	    end = len;
+    	} else if (end < 0) {
+    	    end += len;
+    	}
+    
+        if (end < 0) {
+            end = 0;
+        }
+    	if (start < 0) {
+    	    start += len;   
+    	}
+    	if (start < 0) {
+    		start = 0;
+    	}
+	
+    	return [start, end, len];
+    },
+    
+    _stringTailMatch: function (str, substr, start, end, fromStart) {
+        var m = MochiKit.Base;
+        
+        var len = str.length;
+        var slen = substr.length;
+    
+        var indices = m._adjustIndices(start, end, len);
+        start = indices[0]; end = indices[1]; len = indices[2];
+    
+        if (fromStart) {
+            if (start + slen > len) {
+                return false;
+            }
+        } else {
+            if (end - start < slen || start > len) {
+                return false;
+            }
+            if (end - slen > start) {
+                start = end - slen;
+            }
+        }
+    
+        if (end - start >= slen) {
+            return str.substr(start, slen) == substr;
+        }
+        return false;
+    },
+    
+    /** @id MochiKit.Base.startsWith */
+    startsWith: function (str, prefix, /* optional */ start, /* optional */ end) {
+        if (arguments.length < 2) {
+            throw new TypeError('startsWith() requires at least 2 arguments');
+        }
+        
+        var m = MochiKit.Base;
+        if (m.isUndefinedOrNull(start)) {
+            start = 0;
+        }
+        if (m.isUndefinedOrNull(end)) {
+            end = Number.MAX_VALUE;
+        }
+        
+        if (typeof prefix == "object") {
+            // assume is an array
+            for (var i = 0, j = prefix.length; i < j; i++) {
+                var res = m._stringTailMatch(str, prefix[i], start, end, true);
+                if (res) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        
+        return m._stringTailMatch(str, prefix, start, end, true);
+    },
+    
+    /** @id MochiKit.Base.endsWith */
+    endsWith: function (str, suffix, /* optional */ start, /* optional */ end) {
+        var m = MochiKit.Base;
+        if (m.isUndefinedOrNull(start)) {
+            start = 0;
+        }
+        if (m.isUndefinedOrNull(end)) {
+            end = Number.MAX_VALUE;
+        }
+        
+        if (typeof suffix == "object") {
+            // assume is an array
+            for (var i = 0, j = suffix.length; i < j; i++) {
+                var res = m._stringTailMatch(str, suffix[i], start, end, false);
+                if (res) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        
+        return m._stringTailMatch(str, suffix, start, end, false);
     }
 });
     
@@ -1239,7 +1339,9 @@
     "method",
     "average",
     "mean",
-    "median"
+    "median",
+    "startsWith",
+    "endsWith"
 ];
 
 MochiKit.Base.EXPORT_OK = [
