Home | History | Annotate | Download | only in scripts
      1 /*
      2  * Copyright (C) 2011 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
     14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
     15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
     17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
     23  * THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 var base = base || {};
     27 
     28 (function(){
     29 
     30 base.endsWith = function(string, suffix)
     31 {
     32     if (suffix.length > string.length)
     33         return false;
     34     var expectedIndex = string.length - suffix.length;
     35     return string.lastIndexOf(suffix) == expectedIndex;
     36 };
     37 
     38 base.joinPath = function(parent, child)
     39 {
     40     if (parent.length == 0)
     41         return child;
     42     return parent + '/' + child;
     43 };
     44 
     45 base.dirName = function(path)
     46 {
     47     var directoryIndex = path.lastIndexOf('/');
     48     if (directoryIndex == -1)
     49         return path;
     50     return path.substr(0, directoryIndex);
     51 };
     52 
     53 base.trimExtension = function(url)
     54 {
     55     var index = url.lastIndexOf('.');
     56     if (index == -1)
     57         return url;
     58     return url.substr(0, index);
     59 }
     60 
     61 base.uniquifyArray = function(array)
     62 {
     63     var seen = {};
     64     var result = [];
     65     $.each(array, function(index, value) {
     66         if (seen[value])
     67             return;
     68         seen[value] = true;
     69         result.push(value);
     70     });
     71     return result;
     72 };
     73 
     74 base.flattenArray = function(arrayOfArrays)
     75 {
     76     if (!arrayOfArrays.length)
     77         return [];
     78     return arrayOfArrays.reduce(function(left, right) {
     79         return left.concat(right);
     80     });
     81 };
     82 
     83 base.filterDictionary = function(dictionary, predicate)
     84 {
     85     var result = {};
     86 
     87     for (var key in dictionary) {
     88         if (predicate(key))
     89             result[key] = dictionary[key];
     90     }
     91 
     92     return result;
     93 };
     94 
     95 base.mapDictionary = function(dictionary, functor)
     96 {
     97     var result = {};
     98 
     99     for (var key in dictionary) {
    100         var value = functor(dictionary[key]);
    101         if (typeof value !== 'undefined')
    102             result[key] = value;
    103     }
    104 
    105     return result;
    106 };
    107 
    108 base.filterTree = function(tree, isLeaf, predicate)
    109 {
    110     var filteredTree = {};
    111 
    112     function walkSubtree(subtree, directory)
    113     {
    114         for (var childName in subtree) {
    115             var child = subtree[childName];
    116             var childPath = base.joinPath(directory, childName);
    117             if (isLeaf(child)) {
    118                 if (predicate(child))
    119                     filteredTree[childPath] = child;
    120                 continue;
    121             }
    122             walkSubtree(child, childPath);
    123         }
    124     }
    125 
    126     walkSubtree(tree, '');
    127     return filteredTree;
    128 };
    129 
    130 base.forEachDirectory = function(pathList, callback)
    131 {
    132     var pathsByDirectory = {};
    133     pathList.forEach(function(path) {
    134         var directory = base.dirName(path);
    135         pathsByDirectory[directory] = pathsByDirectory[directory] || [];
    136         pathsByDirectory[directory].push(path);
    137     });
    138     Object.keys(pathsByDirectory).sort().forEach(function(directory) {
    139         var paths = pathsByDirectory[directory];
    140         callback(directory + ' (' + paths.length + ' tests)', paths);
    141     });
    142 };
    143 
    144 base.parseJSONP = function(jsonp)
    145 {
    146     if (!jsonp)
    147         return {};
    148 
    149     if (!jsonp.match(/^[^{[]*\(/))
    150         return JSON.parse(jsonp);
    151 
    152     var startIndex = jsonp.indexOf('(') + 1;
    153     var endIndex = jsonp.lastIndexOf(')');
    154     if (startIndex == 0 || endIndex == -1)
    155         return {};
    156     return JSON.parse(jsonp.substr(startIndex, endIndex - startIndex));
    157 };
    158 
    159 // This is effectively a cache of possibly-resolved promises.
    160 base.AsynchronousCache = function(fetch)
    161 {
    162     this._fetch = fetch;
    163     this._promiseCache = {};
    164 };
    165 
    166 base.AsynchronousCache._sentinel = new Object();
    167 base.AsynchronousCache.prototype.get = function(key)
    168 {
    169     if (!(key in this._promiseCache)) {
    170         this._promiseCache[key] = base.AsynchronousCache._sentinel;
    171         this._promiseCache[key] = this._fetch.call(null, key);
    172     }
    173     if (this._promiseCache[key] === base.AsynchronousCache._sentinel)
    174         return Promise.reject(Error("Reentrant request for ", key));
    175 
    176     return this._promiseCache[key];
    177 };
    178 
    179 base.AsynchronousCache.prototype.clear = function()
    180 {
    181     this._promiseCache = {};
    182 };
    183 
    184 /*
    185     Maintains a dictionary of items, tracking their updates and removing items that haven't been updated.
    186     An "update" is a call to the "update" method.
    187     To remove stale items, call the "remove" method. It will remove all
    188     items that have not been been updated since the last call of "remove".
    189 */
    190 base.UpdateTracker = function()
    191 {
    192     this._items = {};
    193     this._updated = {};
    194 }
    195 
    196 base.UpdateTracker.prototype = {
    197     /*
    198         Update an {key}/{item} pair. You can make the dictionary act as a set and
    199         skip the {item}, in which case the {key} is also the {item}.
    200     */
    201     update: function(key, object)
    202     {
    203         object = object || key;
    204         this._items[key] = object;
    205         this._updated[key] = 1;
    206     },
    207     exists: function(key)
    208     {
    209         return !!this.get(key);
    210     },
    211     get: function(key)
    212     {
    213         return this._items[key];
    214     },
    215     length: function()
    216     {
    217         return Object.keys(this._items).length;
    218     },
    219     /*
    220         Callback parameters are:
    221         - item
    222         - key
    223         - updated, which is true if the item was updated after last purge() call.
    224     */
    225     forEach: function(callback, thisObject)
    226     {
    227         if (!callback)
    228             return;
    229 
    230         Object.keys(this._items).sort().forEach(function(key) {
    231             var item = this._items[key];
    232             callback.call(thisObject || item, item, key, !!this._updated[key]);
    233         }, this);
    234     },
    235     purge: function(removeCallback, thisObject) {
    236         removeCallback = removeCallback || function() {};
    237         this.forEach(function(item, key, updated) {
    238             if (updated)
    239                 return;
    240             removeCallback.call(thisObject || item, item);
    241             delete this._items[key];
    242         }, this);
    243         this._updated = {};
    244     }
    245 }
    246 
    247 // Based on http://src.chromium.org/viewvc/chrome/trunk/src/chrome/browser/resources/shared/js/cr/ui.js
    248 base.extends = function(base, prototype)
    249 {
    250     var extended = function() {
    251         var element = typeof base == 'string' ? document.createElement(base) : base.call(this);
    252         extended.prototype.__proto__ = element.__proto__;
    253         element.__proto__ = extended.prototype;
    254         var singleton = element.init && element.init.apply(element, arguments);
    255         if (singleton)
    256             return singleton;
    257         return element;
    258     }
    259 
    260     extended.prototype = prototype;
    261     return extended;
    262 }
    263 
    264 base.getURLParameter = function(name)
    265 {
    266     var match = RegExp(name + '=' + '(.+?)(&|$)').exec(location.search);
    267     if (!match)
    268         return null;
    269     return decodeURI(match[1])
    270 }
    271 
    272 base.underscoredBuilderName = function(builderName)
    273 {
    274     return builderName.replace(/[ .()]/g, '_');
    275 }
    276 
    277 })();
    278