item lister resources put in an appropriate subdir; other related tidies

code was very ad hoc, but also polluting the root of the all jar; now resources 
at least are in a clean subdir


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/461ac9f1
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/461ac9f1
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/461ac9f1

Branch: refs/heads/master
Commit: 461ac9f1f318b31558a45c011332803379632805
Parents: 1b080fb
Author: Alex Heneveld <[email protected]>
Authored: Wed Apr 29 13:32:34 2015 +0100
Committer: Alex Heneveld <[email protected]>
Committed: Fri May 8 18:22:22 2015 +0100

----------------------------------------------------------------------
 usage/cli/pom.xml                               |   5 +-
 .../src/main/java/brooklyn/cli/ItemLister.java  |  77 +-
 .../java/brooklyn/cli/lister/ClassFinder.java   |   7 +-
 .../brooklyn/cli/lister/ItemDescriptors.java    |  20 +-
 .../main/resources/brooklyn-object-list.html    | 147 ----
 .../statics/brooklyn-object-list.html           | 147 ++++
 .../brooklyn/item-lister/statics/common.js      |  94 +++
 .../brooklyn/item-lister/statics/items.css      | 153 ++++
 .../statics/style/js/catalog/bloodhound.js      | 727 +++++++++++++++++++
 .../statics/style/js/underscore-min.js          |   6 +
 .../statics/style/js/underscore-min.map         |   1 +
 .../item-lister/templates/enricher.html         |  59 ++
 .../brooklyn/item-lister/templates/entity.html  |  66 ++
 .../item-lister/templates/location.html         |  62 ++
 .../brooklyn/item-lister/templates/policy.html  |  59 ++
 usage/cli/src/main/resources/common.js          |  94 ---
 usage/cli/src/main/resources/enricher.html      |  59 --
 usage/cli/src/main/resources/entity.html        |  66 --
 usage/cli/src/main/resources/items.css          | 153 ----
 .../src/main/resources/libs/js/bloodhound.js    | 727 -------------------
 usage/cli/src/main/resources/location.html      |  62 --
 usage/cli/src/main/resources/policy.html        |  59 --
 22 files changed, 1457 insertions(+), 1393 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/461ac9f1/usage/cli/pom.xml
----------------------------------------------------------------------
diff --git a/usage/cli/pom.xml b/usage/cli/pom.xml
index 1042649..451b0ed 100644
--- a/usage/cli/pom.xml
+++ b/usage/cli/pom.xml
@@ -159,11 +159,12 @@
                     <configuration>
                         <excludes combine.children="append">
                             <!--
-                                bloodhound.js is copyright Twitter, Inc. It is 
included in NOTICE.
+                                bloodhound.js is copyright Twitter, Inc.  
underscore is copyright Jeremy Ashkenas, DocumentCloud.
+                                Both are included in the LICENSE notes.
                                 It is used by the HTML generated by the CLI 
tool for entity descriptions,
                                 i.e. by brooklyn.cli.itemlister.ItemLister
                             -->
-                            
<exclude>**/src/main/resources/libs/js/bloodhound.js</exclude>
+                            
<exclude>**/src/main/resources/brooklyn/item-lister/statics/style/js/**</exclude>
                         </excludes>
                     </configuration>
                 </plugin>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/461ac9f1/usage/cli/src/main/java/brooklyn/cli/ItemLister.java
----------------------------------------------------------------------
diff --git a/usage/cli/src/main/java/brooklyn/cli/ItemLister.java 
b/usage/cli/src/main/java/brooklyn/cli/ItemLister.java
index 3c567dc..e616fb5 100644
--- a/usage/cli/src/main/java/brooklyn/cli/ItemLister.java
+++ b/usage/cli/src/main/java/brooklyn/cli/ItemLister.java
@@ -22,8 +22,10 @@ import io.airlift.command.Command;
 import io.airlift.command.Option;
 
 import java.io.File;
+import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.ServiceLoader;
@@ -43,6 +45,7 @@ import brooklyn.policy.Enricher;
 import brooklyn.policy.Policy;
 import brooklyn.util.ResourceUtils;
 import brooklyn.util.collections.MutableSet;
+import brooklyn.util.net.Urls;
 import brooklyn.util.os.Os;
 import brooklyn.util.text.Strings;
 import brooklyn.util.text.TemplateProcessor;
@@ -54,7 +57,6 @@ import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializationFeature;
-
 import com.google.common.base.Charsets;
 import com.google.common.base.Splitter;
 import com.google.common.collect.FluentIterable;
@@ -66,6 +68,9 @@ import com.google.common.io.Files;
 public class ItemLister {
     
     private static final Logger LOG = 
LoggerFactory.getLogger(ItemLister.class);
+    private static final String BASE = "brooklyn/item-lister";
+    private static final String BASE_TEMPLATES = BASE+"/"+"templates";
+    private static final String BASE_STATICS = BASE+"/"+"statics";
 
     @Command(name = "list-objects", description = "List Brooklyn objects 
(Entities, Policies, Enrichers and Locations)")
     public static class ListAllCommand extends 
AbstractMain.BrooklynCommandCollectingArgs {
@@ -91,8 +96,8 @@ public class ItemLister {
         @SuppressWarnings("unchecked")
         @Override
         public Void call() throws Exception {
-            LOG.info("Retrieving objects");
             List<URL> urls = getUrls();
+            LOG.info("Retrieving objects from "+urls);
 
             // TODO Remove duplication from separate ListPolicyCommand etc
             List<Class<? extends Entity>> entityTypes = getTypes(urls, 
Entity.class);
@@ -113,48 +118,63 @@ public class ItemLister {
             if (outputFolder == null) {
                 System.out.println(json);
             } else {
-                LOG.info("Outputting item list to " + outputFolder);
+                LOG.info("Outputting item list (size "+itemCount+") to " + 
outputFolder);
                 String outputPath = Os.mergePaths(outputFolder, "index.html");
                 String parentDir = (new 
File(outputPath).getParentFile()).getAbsolutePath();
                 mkdir(parentDir, "entities");
                 mkdir(parentDir, "policies");
                 mkdir(parentDir, "enrichers");
                 mkdir(parentDir, "locations");
-                mkdir(parentDir, "locationResolvers");
+                mkdir(parentDir, "locationResolvers"); //TODO nothing written 
here yet...
+                
+                mkdir(parentDir, "style");
+                mkdir(Os.mergePaths(parentDir, "style"), "js");
+                mkdir(Os.mergePaths(parentDir, "style", "js"), "catalog");
+                
                 Files.write("var items = " + json, new 
File(Os.mergePaths(outputFolder, "items.js")), Charsets.UTF_8);
                 ResourceUtils resourceUtils = ResourceUtils.create(this);
-                String js = resourceUtils.getResourceAsString("common.js");
-                Files.write(js, new File(Os.mergePaths(outputFolder, 
"common.js")), Charsets.UTF_8);
-                String css = resourceUtils.getResourceAsString("items.css");
-                Files.write(css, new File(Os.mergePaths(outputFolder, 
"items.css")), Charsets.UTF_8);
-                String mainHtml = 
resourceUtils.getResourceAsString("brooklyn-object-list.html");
-                Files.write(mainHtml, new File(Os.mergePaths(outputFolder, 
"index.html")), Charsets.UTF_8);
+                
+                // root - just loads the above JSON
+                
copyFromItemListerClasspathBaseStaticsToOutputDir(resourceUtils, 
"brooklyn-object-list.html", "index.html");
+                
+                // statics - structure mirrors docs (not for any real reason 
however... the json is usually enough for our docs)
+                
copyFromItemListerClasspathBaseStaticsToOutputDir(resourceUtils, "common.js");
+                
copyFromItemListerClasspathBaseStaticsToOutputDir(resourceUtils, "items.css");
+                
copyFromItemListerClasspathBaseStaticsToOutputDir(resourceUtils, 
"style/js/underscore-min.js");
+                
copyFromItemListerClasspathBaseStaticsToOutputDir(resourceUtils, 
"style/js/underscore-min.map");
+                
copyFromItemListerClasspathBaseStaticsToOutputDir(resourceUtils, 
"style/js/catalog/bloodhound.js");
+
+                // now make pages for each item
+                
                 List<Map<String, Object>> entities = (List<Map<String, 
Object>>) result.get("entities");
-                String entityTemplateHtml = 
resourceUtils.getResourceAsString("entity.html");
+                String entityTemplateHtml = 
resourceUtils.getResourceAsString(Urls.mergePaths(BASE_TEMPLATES, 
"entity.html"));
                 for (Map<String, Object> entity : entities) {
                     String type = (String) entity.get("type");
                     String name = (String) entity.get("name");
                     String entityHtml = 
TemplateProcessor.processTemplateContents(entityTemplateHtml, 
ImmutableMap.of("type", type, "name", name));
                     Files.write(entityHtml, new 
File(Os.mergePaths(outputFolder, "entities", type + ".html")), Charsets.UTF_8);
                 }
+                
                 List<Map<String, Object>> policies = (List<Map<String, 
Object>>) result.get("policies");
-                String policyTemplateHtml = 
resourceUtils.getResourceAsString("policy.html");
+                String policyTemplateHtml = 
resourceUtils.getResourceAsString(Urls.mergePaths(BASE_TEMPLATES, 
"policy.html"));
                 for (Map<String, Object> policy : policies) {
                     String type = (String) policy.get("type");
                     String name = (String) policy.get("name");
                     String policyHtml = 
TemplateProcessor.processTemplateContents(policyTemplateHtml, 
ImmutableMap.of("type", type, "name", name));
                     Files.write(policyHtml, new 
File(Os.mergePaths(outputFolder, "policies", type + ".html")), Charsets.UTF_8);
                 }
+                
                 List<Map<String, Object>> enrichers = (List<Map<String, 
Object>>) result.get("enrichers");
-                String enricherTemplateHtml = 
resourceUtils.getResourceAsString("enricher.html");
+                String enricherTemplateHtml = 
resourceUtils.getResourceAsString(Urls.mergePaths(BASE_TEMPLATES, 
"enricher.html"));
                 for (Map<String, Object> enricher : enrichers) {
                     String type = (String) enricher.get("type");
                     String name = (String) enricher.get("name");
                     String enricherHtml = 
TemplateProcessor.processTemplateContents(enricherTemplateHtml, 
ImmutableMap.of("type", type, "name", name));
                     Files.write(enricherHtml, new 
File(Os.mergePaths(outputFolder, "enrichers", type + ".html")), Charsets.UTF_8);
                 }
+                
                 List<Map<String, Object>> locations = (List<Map<String, 
Object>>) result.get("locations");
-                String locationTemplateHtml = 
resourceUtils.getResourceAsString("location.html");
+                String locationTemplateHtml = 
resourceUtils.getResourceAsString(Urls.mergePaths(BASE_TEMPLATES, 
"location.html"));
                 for (Map<String, Object> location : locations) {
                     String type = (String) location.get("type");
                     String locationHtml = 
TemplateProcessor.processTemplateContents(locationTemplateHtml, 
ImmutableMap.of("type", type));
@@ -165,6 +185,14 @@ public class ItemLister {
             return null;
         }
 
+        private void 
copyFromItemListerClasspathBaseStaticsToOutputDir(ResourceUtils resourceUtils, 
String item) throws IOException {
+            copyFromItemListerClasspathBaseStaticsToOutputDir(resourceUtils, 
item, item);
+        }
+        private void 
copyFromItemListerClasspathBaseStaticsToOutputDir(ResourceUtils resourceUtils, 
String item, String dest) throws IOException {
+            String js = 
resourceUtils.getResourceAsString(Urls.mergePaths(BASE_STATICS, item));
+            Files.write(js, new File(Os.mergePaths(outputFolder, dest)), 
Charsets.UTF_8);
+        }
+
         private void mkdir(String rootDir, String dirName) {
             (new File(Os.mergePaths(rootDir, dirName))).mkdirs();
         }
@@ -185,7 +213,10 @@ public class ItemLister {
                 }
             } else {
                 for (String jar : jars) {
-                    urls.addAll(ClassFinder.toJarUrls(jar));
+                    List<URL> expanded = ClassFinder.toJarUrls(jar);
+                    if (expanded.isEmpty())
+                        LOG.warn("No jars found at: "+jar);
+                    urls.addAll(expanded);
                 }
             }
             return urls;
@@ -194,6 +225,8 @@ public class ItemLister {
         private <T extends BrooklynObject> List<Class<? extends T>> 
getTypes(List<URL> urls, Class<T> type) {
             return getTypes(urls, type, null);
         }
+
+        int itemCount = 0;
         
         private <T extends BrooklynObject> List<Class<? extends T>> 
getTypes(List<URL> urls, Class<T> type, Boolean catalogOnlyOverride) {
             FluentIterable<Class<? extends T>> fluent = 
FluentIterable.from(ClassFinder.findClasses(urls, type));
@@ -203,19 +236,21 @@ public class ItemLister {
             if (catalogOnlyOverride == null ? catalogOnly : 
catalogOnlyOverride) {
                 fluent = 
fluent.filter(ClassFinder.withAnnotation(Catalog.class));
             }
-            ImmutableList<Class<? extends T>> result = fluent.toList();
+            List<Class<? extends T>> filtered = fluent.toList();
+            Collection<Class<? extends T>> result;
             if (ignoreImpls) {
-                MutableSet<Class<? extends T>> mutableResult = 
MutableSet.copyOf(result);
-                for (Class<? extends T> clazz : result) {
+                result = MutableSet.copyOf(filtered);
+                for (Class<? extends T> clazz : filtered) {
                     ImplementedBy implementedBy = 
clazz.getAnnotation(ImplementedBy.class);
                     if (implementedBy != null) {
-                        mutableResult.remove(implementedBy.value());
+                        result.remove(implementedBy.value());
                     }
                 }
-                return ImmutableList.copyOf(mutableResult);
             } else {
-                return result;
+                result = filtered;
             }
+            itemCount += result.size();
+            return ImmutableList.copyOf(result);
         }
         
         private String toJson(Object obj) throws JsonProcessingException {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/461ac9f1/usage/cli/src/main/java/brooklyn/cli/lister/ClassFinder.java
----------------------------------------------------------------------
diff --git a/usage/cli/src/main/java/brooklyn/cli/lister/ClassFinder.java 
b/usage/cli/src/main/java/brooklyn/cli/lister/ClassFinder.java
index 101e019..a66fa7d 100644
--- a/usage/cli/src/main/java/brooklyn/cli/lister/ClassFinder.java
+++ b/usage/cli/src/main/java/brooklyn/cli/lister/ClassFinder.java
@@ -49,6 +49,7 @@ import brooklyn.util.javalang.UrlClassLoader;
 import brooklyn.util.net.Urls;
 import brooklyn.util.os.Os;
 
+import com.google.common.annotations.Beta;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
@@ -91,7 +92,9 @@ public class ClassFinder {
             }
         };
     }
-    
+
+    /** finds a jar at a url, or for directories, jars under a path */
+    @Beta
     public static List<URL> toJarUrls(String url) throws MalformedURLException 
{
         if (url==null) throw new NullPointerException("Cannot read from null");
         if (url=="") throw new NullPointerException("Cannot read from empty 
string");
@@ -126,6 +129,8 @@ public class ClassFinder {
                         log.info("Cannot read "+file+"; not a file or 
directory");
                     }
                 }
+            } else {
+                result.add(new URL("file://"+tidiedFile));
             }
         }
         

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/461ac9f1/usage/cli/src/main/java/brooklyn/cli/lister/ItemDescriptors.java
----------------------------------------------------------------------
diff --git a/usage/cli/src/main/java/brooklyn/cli/lister/ItemDescriptors.java 
b/usage/cli/src/main/java/brooklyn/cli/lister/ItemDescriptors.java
index 3039154..989a29d 100644
--- a/usage/cli/src/main/java/brooklyn/cli/lister/ItemDescriptors.java
+++ b/usage/cli/src/main/java/brooklyn/cli/lister/ItemDescriptors.java
@@ -24,6 +24,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import brooklyn.basic.BrooklynDynamicType;
 import brooklyn.basic.BrooklynObject;
 import brooklyn.basic.BrooklynType;
@@ -42,6 +45,8 @@ import brooklyn.rest.domain.SummaryComparators;
 import brooklyn.rest.transform.EffectorTransformer;
 import brooklyn.rest.transform.EntityTransformer;
 import brooklyn.rest.transform.SensorTransformer;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.exceptions.RuntimeInterruptedException;
 
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
@@ -50,6 +55,8 @@ import com.google.common.collect.Sets;
 
 public class ItemDescriptors {
 
+    private static final Logger LOG = 
LoggerFactory.getLogger(ItemDescriptors.class);
+    
     public static List<Map<String, Object>> toItemDescriptors(Iterable<? 
extends Class<? extends BrooklynObject>> types, boolean headingsOnly) {
         return toItemDescriptors(types, headingsOnly, null);
     }
@@ -58,8 +65,17 @@ public class ItemDescriptors {
         List<Map<String, Object>> itemDescriptors = Lists.newArrayList();
         
         for (Class<? extends BrooklynObject> type : types) {
-            Map<String, Object> itemDescriptor = toItemDescriptor(type, 
headingsOnly);
-            itemDescriptors.add(itemDescriptor);
+            try {
+                Map<String, Object> itemDescriptor = toItemDescriptor(type, 
headingsOnly);
+                itemDescriptors.add(itemDescriptor);
+            } catch (Throwable throwable) {
+                if (throwable instanceof InterruptedException)
+                    throw new 
RuntimeInterruptedException((InterruptedException) throwable);
+                if (throwable instanceof RuntimeInterruptedException)
+                    throw (RuntimeInterruptedException) throwable;
+
+                LOG.warn("Could not load "+type+": "+throwable);
+            }
         }
         
         if (!Strings.isNullOrEmpty(sortField)) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/461ac9f1/usage/cli/src/main/resources/brooklyn-object-list.html
----------------------------------------------------------------------
diff --git a/usage/cli/src/main/resources/brooklyn-object-list.html 
b/usage/cli/src/main/resources/brooklyn-object-list.html
deleted file mode 100644
index 4487346..0000000
--- a/usage/cli/src/main/resources/brooklyn-object-list.html
+++ /dev/null
@@ -1,147 +0,0 @@
-<!DOCTYPE html>
-<!--
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you 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.
--->
-<html lang="en">
-  <head>
-    <meta charset="utf-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <meta name="viewport" content="width=device-width, initial-scale=1">
-    <link rel="stylesheet" 
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css";>
-    <link rel="stylesheet" href="items.css" type="text/css" media="screen"/>
-    <title>Brooklyn Objects</title>
-  </head>
-
-  <body>
-    <div id="container">
-      <div id="header">
-        <div id="identity">
-          <a href="https://brooklyn.incubator.apache.org/"; 
rel="home">Brooklyn</a>
-        </div>
-      </div>
-
-      <ul class="nav nav-tabs">
-        <li class="active"><a href="#entities" 
data-toggle="tab">Entities</a></li>
-        <li><a href="#policies" data-toggle="tab">Policies</a></li>
-        <li><a href="#enrichers" data-toggle="tab">Enrichers</a></li>
-      </ul>
-
-      <div class="tab-content">
-        <div class="tab-pane active" id="entities">
-          <input class="filter form-control" type="text" placeholder="Filter 
by type, e.g. webapp or nosql">
-        </div>
-        <div class="tab-pane" id="policies">
-          <input class="filter form-control" type="text" placeholder="Filter 
by type, e.g. ha">
-        </div>
-        <div class="tab-pane" id="enrichers">
-          <input class="filter form-control" type="text" placeholder="Filter 
by type, e.g. http">
-        </div>
-        <div class="tab-pane" id="locations"></div>
-        <div class="tab-pane" id="locationResolvers"></div>
-      </div>
-    </div>
-
-    <script 
src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"; 
type="text/javascript"></script>
-    <script src="../../style/js/underscore-min.js" 
type="text/javascript"></script>
-    <script 
src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js";></script>
-    <script src="libs/js/bloodhound.js" type="text/javascript"></script>
-    <script src="common.js" type="text/javascript"></script>
-    <script src="items.js" type="text/javascript"></script>
-    <script type="text/javascript">
-        if (!String.prototype.trim) {
-            String.prototype.trim = function () {
-                return this.replace(/^\s+|\s+$/g, '');
-            };
-        }
-        var card = function (collection, cardFunction, target) {
-            var cards = _.map(collection, cardFunction);
-            $(target).append(cards.join(""));
-        };
-        var ESCAPE_KEY = 27;
-
-        var filter = function (element) {
-            var $element = $(element),
-                $tab = $element.parent(),
-                kind = $tab.attr("id"),
-                collection = items[kind];
-            if (!collection) {
-                console.warn("Unable to determine type for input", element);
-                return;
-            }
-
-            // Number.MAX_VALUE configures Bloodhound to return all matches.
-            var bloodhound = new Bloodhound({
-                name: kind,
-                local: collection,
-                limit: Number.MAX_VALUE,
-                datumTokenizer: function (d) {
-                    return Bloodhound.tokenizers.nonword(d.type);
-                },
-                queryTokenizer: Bloodhound.tokenizers.nonword
-            });
-            bloodhound.initialize();
-
-            // Filter items as input changes
-            var allAnchors = $tab.find("a").map(function (index, a) { return 
$(a); });
-            var hideAnchorsNotMatchingQuery = function () {
-                var query = $element.val();
-                query = query.trim();
-                if (!query) {
-                    $tab.find("a").removeClass("hide");
-                } else {
-                    var matchedTypes = {};
-                    bloodhound.get(query, function (suggestions) {
-                        _.each(suggestions, function (s) {
-                            // approximate a set!
-                            matchedTypes[s.type] = true;
-                        });
-                    });
-                    _.each(allAnchors, function (a) {
-                        if (_.has(matchedTypes, a.data("type"))) {
-                            a.removeClass("hide");
-                        } else {
-                            a.addClass("hide");
-                        }
-                    });
-                }
-            };
-            $element.on("input", hideAnchorsNotMatchingQuery);
-            // In case page is loaded with text in input, e.g. from back 
button.
-            hideAnchorsNotMatchingQuery();
-
-            $element.on("keydown", function (e) {
-                if (e.keyCode == ESCAPE_KEY) {
-                    $element.val("");
-                    hideAnchorsNotMatchingQuery();
-                }
-            });
-        };
-
-        $(document).ready(function () {
-            card(items.entities, brooklyn.entityCard, "#entities");
-            card(items.policies, brooklyn.policyCard, "#policies");
-            card(items.enrichers, brooklyn.enricherCard, "#enrichers");
-            //transformItemAndAddToElement(items.locations, 
brooklyn.locationCard, "#locations");
-            //items.locationResolvers.forEach(function (element) { 
$("#locationResolvers").append("<tr><td>" + element + "</td></tr>"); });
-            $("input.filter").each(function (index, element) {
-                filter(element);
-            });
-        });
-    </script>
-  </body>
-</html>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/461ac9f1/usage/cli/src/main/resources/brooklyn/item-lister/statics/brooklyn-object-list.html
----------------------------------------------------------------------
diff --git 
a/usage/cli/src/main/resources/brooklyn/item-lister/statics/brooklyn-object-list.html
 
b/usage/cli/src/main/resources/brooklyn/item-lister/statics/brooklyn-object-list.html
new file mode 100644
index 0000000..c30d3db
--- /dev/null
+++ 
b/usage/cli/src/main/resources/brooklyn/item-lister/statics/brooklyn-object-list.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+-->
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <link rel="stylesheet" 
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css";>
+    <link rel="stylesheet" href="items.css" type="text/css" media="screen"/>
+    <title>Brooklyn Objects</title>
+  </head>
+
+  <body>
+    <div id="container">
+      <div id="header">
+        <div id="identity">
+          <a href="https://brooklyn.incubator.apache.org/"; 
rel="home">Brooklyn</a>
+        </div>
+      </div>
+
+      <ul class="nav nav-tabs">
+        <li class="active"><a href="#entities" 
data-toggle="tab">Entities</a></li>
+        <li><a href="#policies" data-toggle="tab">Policies</a></li>
+        <li><a href="#enrichers" data-toggle="tab">Enrichers</a></li>
+      </ul>
+
+      <div class="tab-content">
+        <div class="tab-pane active" id="entities">
+          <input class="filter form-control" type="text" placeholder="Filter 
by type, e.g. webapp or nosql">
+        </div>
+        <div class="tab-pane" id="policies">
+          <input class="filter form-control" type="text" placeholder="Filter 
by type, e.g. ha">
+        </div>
+        <div class="tab-pane" id="enrichers">
+          <input class="filter form-control" type="text" placeholder="Filter 
by type, e.g. http">
+        </div>
+        <div class="tab-pane" id="locations"></div>
+        <div class="tab-pane" id="locationResolvers"></div>
+      </div>
+    </div>
+
+    <script 
src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"; 
type="text/javascript"></script>
+    <script src="style/js/underscore-min.js" type="text/javascript"></script>
+    <script 
src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js";></script>
+    <script src="style/js/catalog/bloodhound.js" 
type="text/javascript"></script>
+    <script src="common.js" type="text/javascript"></script>
+    <script src="items.js" type="text/javascript"></script>
+    <script type="text/javascript">
+        if (!String.prototype.trim) {
+            String.prototype.trim = function () {
+                return this.replace(/^\s+|\s+$/g, '');
+            };
+        }
+        var card = function (collection, cardFunction, target) {
+            var cards = _.map(collection, cardFunction);
+            $(target).append(cards.join(""));
+        };
+        var ESCAPE_KEY = 27;
+
+        var filter = function (element) {
+            var $element = $(element),
+                $tab = $element.parent(),
+                kind = $tab.attr("id"),
+                collection = items[kind];
+            if (!collection) {
+                console.warn("Unable to determine type for input", element);
+                return;
+            }
+
+            // Number.MAX_VALUE configures Bloodhound to return all matches.
+            var bloodhound = new Bloodhound({
+                name: kind,
+                local: collection,
+                limit: Number.MAX_VALUE,
+                datumTokenizer: function (d) {
+                    return Bloodhound.tokenizers.nonword(d.type);
+                },
+                queryTokenizer: Bloodhound.tokenizers.nonword
+            });
+            bloodhound.initialize();
+
+            // Filter items as input changes
+            var allAnchors = $tab.find("a").map(function (index, a) { return 
$(a); });
+            var hideAnchorsNotMatchingQuery = function () {
+                var query = $element.val();
+                query = query.trim();
+                if (!query) {
+                    $tab.find("a").removeClass("hide");
+                } else {
+                    var matchedTypes = {};
+                    bloodhound.get(query, function (suggestions) {
+                        _.each(suggestions, function (s) {
+                            // approximate a set!
+                            matchedTypes[s.type] = true;
+                        });
+                    });
+                    _.each(allAnchors, function (a) {
+                        if (_.has(matchedTypes, a.data("type"))) {
+                            a.removeClass("hide");
+                        } else {
+                            a.addClass("hide");
+                        }
+                    });
+                }
+            };
+            $element.on("input", hideAnchorsNotMatchingQuery);
+            // In case page is loaded with text in input, e.g. from back 
button.
+            hideAnchorsNotMatchingQuery();
+
+            $element.on("keydown", function (e) {
+                if (e.keyCode == ESCAPE_KEY) {
+                    $element.val("");
+                    hideAnchorsNotMatchingQuery();
+                }
+            });
+        };
+
+        $(document).ready(function () {
+            card(items.entities, brooklyn.entityCard, "#entities");
+            card(items.policies, brooklyn.policyCard, "#policies");
+            card(items.enrichers, brooklyn.enricherCard, "#enrichers");
+            //transformItemAndAddToElement(items.locations, 
brooklyn.locationCard, "#locations");
+            //items.locationResolvers.forEach(function (element) { 
$("#locationResolvers").append("<tr><td>" + element + "</td></tr>"); });
+            $("input.filter").each(function (index, element) {
+                filter(element);
+            });
+        });
+    </script>
+  </body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/461ac9f1/usage/cli/src/main/resources/brooklyn/item-lister/statics/common.js
----------------------------------------------------------------------
diff --git 
a/usage/cli/src/main/resources/brooklyn/item-lister/statics/common.js 
b/usage/cli/src/main/resources/brooklyn/item-lister/statics/common.js
new file mode 100644
index 0000000..8e8a45f
--- /dev/null
+++ b/usage/cli/src/main/resources/brooklyn/item-lister/statics/common.js
@@ -0,0 +1,94 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+var brooklyn = (function ($, _) {
+
+    return {
+        findItemOfType: function(items, type) {
+            return _.findWhere(items, { type: type });
+        },
+
+        entityCard: _.template(
+            "<a class='plain' data-type='<%= type %>' href='entities/<%= type 
%>.html'>" +
+            "<div class='card'>" +
+            "<span class='glyphicon glyphicon-chevron-right'/>" +
+            "<div class='name'><%=name%></div>" +
+            "<div class='type'><%=type%></div>" +
+            "<div class='description'><%=description%></div>" +
+            "</div>" +
+            "</a>"
+        ),
+        policyCard: _.template(
+            "<a class='plain' data-type='<%= type %>' href='policies/<%= type 
%>.html'>" +
+            "<div class='card'>" +
+            "<span class='glyphicon glyphicon-chevron-right'/>" +
+            "<div class='name'><%=name%></div>" +
+            "<div class='type'><%=type%></div>" +
+            "<div class='description'><%=description%></div>" +
+            "</div>" +
+            "</a>"
+        ),
+        enricherCard: _.template(
+            "<a class='plain' data-type='<%= type %>' href='enrichers/<%= type 
%>.html'>" +
+            "<div class='card'>" +
+            "<span class='glyphicon glyphicon-chevron-right'/>" +
+            "<div class='name'><%=name%></div>" +
+            "<div class='type'><%=type%></div>" +
+            "<div class='description'><%=description%></div>" +
+            "</div>" +
+            "</a>"
+        ),
+
+        typeSummary: _.template(
+            "<div class='summaryLabel'><%=name%></div>" +
+            "<div class='summaryType'><%=type%></div>" +
+            "<div class='description'><%=description%></div>"
+        ),
+
+        configKeyCard: _.template(
+            "<div class='card configKey'>" +
+            "<div class='name'><%=name%></div>" +
+            "<dl>" +
+            "<dt>description</dt><dd><%=(description||'&nbsp;')%></dd>" +
+            "<dt>value type</dt><dd class='java'><%=(type||'&nbsp;')%></dd>" +
+            "<dt>default value</dt><dd><%=(defaultValue||'&nbsp;')%></dd>" +
+            "</dl>" +
+            "</div>"
+        ),
+        sensorCard: _.template(
+            "<div class='card sensor'>" +
+            "<div class='name'><%=name%></div>" +
+            "<dl>" +
+            "<dt>description</dt><dd><%=(description||'&nbsp;')%></dd>" +
+            "<dt>value type</dt><dd class='java'><%=(type||'&nbsp;')%></dd>" +
+            "</dl>" +
+            "</div>"
+        ),
+        effectorCard: _.template(
+            "<div class='card effector'>" +
+            "<div class='name'><%=name%></div>" +
+            "<dl>" +
+            "<dt>description</dt><dd><%=(description||'&nbsp;')%></dd>" +
+            "<dt>return type</dt><dd 
class='java'><%=(returnType||'&nbsp;')%></dd>" +
+            "</dl>" +
+            "</div>"
+        )
+    };
+
+}(jQuery, _));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/461ac9f1/usage/cli/src/main/resources/brooklyn/item-lister/statics/items.css
----------------------------------------------------------------------
diff --git 
a/usage/cli/src/main/resources/brooklyn/item-lister/statics/items.css 
b/usage/cli/src/main/resources/brooklyn/item-lister/statics/items.css
new file mode 100644
index 0000000..a0bf0f0
--- /dev/null
+++ b/usage/cli/src/main/resources/brooklyn/item-lister/statics/items.css
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+*/
+/* landing page */
+body {
+  margin: 0px;
+  padding: 10px 0px 20px 0px;
+  font-family: arial, helvetica, sans-serif;
+  background-color: #ffffff;
+  color: #393939;
+  font-size: 15px;
+}
+
+.nav-tabs {
+  clear: both;
+  font-weight: bold;
+  font-size: 12pt;
+}
+.nav-tabs a {
+  color: #4d9d3a;
+}
+.nav-tabs a:hover {
+  color: #4d9d3a;
+}
+
+.tab-content {
+  padding: 20px;
+  padding-bottom: 10px;
+  border: 1px solid #ddd;
+  border-top: none;
+}
+
+a:hover > .card {
+  top: -2px;
+  background-color: #f4f4f4;
+  box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.2);
+}
+.card {
+  position: relative;
+  padding: 12px;
+  background-color: #f8f8f8;
+  color: #333;
+  border: 1px solid #E1E1E8;
+  border-radius: 6px;
+  font-size: 11pt;
+}
+.card,
+.filter {
+    margin-bottom: 10px;
+}
+a .glyphicon {
+  display: block;
+  position: absolute;
+  right: 0;
+  top: 0;
+  padding: 8px;
+  font-size: 16pt;
+  color: #aaa;
+}
+a:hover .glyphicon {
+  color: #888;
+}
+a.plain {
+  text-decoration: none !important;
+}
+.name {
+  font-size: 12pt;
+  font-weight: bold;
+}
+.type {
+  font-family: monospace;
+  color: #888;
+  margin-top: 2px;
+}
+#summary .description {
+  margin: 15px 0 25px 0;
+}
+.card .description {
+  margin: 10px 40px 0 20px;
+}
+
+#summary {
+  clear: both;
+  margin: 10px 0 20px 0;
+}
+.summaryLabel {
+  font-size: 20px;
+  font-weight: bold;
+}
+.summaryType {
+  font-family: monospace;
+  font-size: 12pt;
+  color: #888;
+}
+.java {
+  font-family: monospace;
+}
+
+.card dl {
+  margin-bottom: 0;
+  margin-top: 5px;
+}
+dt {
+  clear: both;
+  float: left;
+  width: 8em;
+  text-align: right;
+  font-weight: normal;
+}
+dd {
+  margin-left: 9em;
+}
+
+#container {
+  width: 980px;
+  padding: 0;
+  margin: 0 auto;
+}
+
+#identity {
+  float: left;
+  margin: 0;
+  padding: 30px 0 15px 10px;
+}
+
+#identity a {
+  text-decoration: none;
+  display: block;
+  margin: 0;
+  color: #4d9d3a;
+  font-size: 2.5em;
+  padding: 0;
+  background: transparent url(images/brooklyn.gif) no-repeat 0 0;
+  width: 206px;
+  height: 44px;
+  text-indent: -1000px;
+  overflow: hidden;
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/461ac9f1/usage/cli/src/main/resources/brooklyn/item-lister/statics/style/js/catalog/bloodhound.js
----------------------------------------------------------------------
diff --git 
a/usage/cli/src/main/resources/brooklyn/item-lister/statics/style/js/catalog/bloodhound.js
 
b/usage/cli/src/main/resources/brooklyn/item-lister/statics/style/js/catalog/bloodhound.js
new file mode 100644
index 0000000..96a4c43
--- /dev/null
+++ 
b/usage/cli/src/main/resources/brooklyn/item-lister/statics/style/js/catalog/bloodhound.js
@@ -0,0 +1,727 @@
+/*!
+ * typeahead.js 0.10.5
+ * https://github.com/twitter/typeahead.js
+ * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT
+ */
+
+(function($) {
+    var _ = function() {
+        "use strict";
+        return {
+            isMsie: function() {
+                return /(msie|trident)/i.test(navigator.userAgent) ? 
navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false;
+            },
+            isBlankString: function(str) {
+                return !str || /^\s*$/.test(str);
+            },
+            escapeRegExChars: function(str) {
+                return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, 
"\\$&");
+            },
+            isString: function(obj) {
+                return typeof obj === "string";
+            },
+            isNumber: function(obj) {
+                return typeof obj === "number";
+            },
+            isArray: $.isArray,
+            isFunction: $.isFunction,
+            isObject: $.isPlainObject,
+            isUndefined: function(obj) {
+                return typeof obj === "undefined";
+            },
+            toStr: function toStr(s) {
+                return _.isUndefined(s) || s === null ? "" : s + "";
+            },
+            bind: $.proxy,
+            each: function(collection, cb) {
+                $.each(collection, reverseArgs);
+                function reverseArgs(index, value) {
+                    return cb(value, index);
+                }
+            },
+            map: $.map,
+            filter: $.grep,
+            every: function(obj, test) {
+                var result = true;
+                if (!obj) {
+                    return result;
+                }
+                $.each(obj, function(key, val) {
+                    if (!(result = test.call(null, val, key, obj))) {
+                        return false;
+                    }
+                });
+                return !!result;
+            },
+            some: function(obj, test) {
+                var result = false;
+                if (!obj) {
+                    return result;
+                }
+                $.each(obj, function(key, val) {
+                    if (result = test.call(null, val, key, obj)) {
+                        return false;
+                    }
+                });
+                return !!result;
+            },
+            mixin: $.extend,
+            getUniqueId: function() {
+                var counter = 0;
+                return function() {
+                    return counter++;
+                };
+            }(),
+            templatify: function templatify(obj) {
+                return $.isFunction(obj) ? obj : template;
+                function template() {
+                    return String(obj);
+                }
+            },
+            defer: function(fn) {
+                setTimeout(fn, 0);
+            },
+            debounce: function(func, wait, immediate) {
+                var timeout, result;
+                return function() {
+                    var context = this, args = arguments, later, callNow;
+                    later = function() {
+                        timeout = null;
+                        if (!immediate) {
+                            result = func.apply(context, args);
+                        }
+                    };
+                    callNow = immediate && !timeout;
+                    clearTimeout(timeout);
+                    timeout = setTimeout(later, wait);
+                    if (callNow) {
+                        result = func.apply(context, args);
+                    }
+                    return result;
+                };
+            },
+            throttle: function(func, wait) {
+                var context, args, timeout, result, previous, later;
+                previous = 0;
+                later = function() {
+                    previous = new Date();
+                    timeout = null;
+                    result = func.apply(context, args);
+                };
+                return function() {
+                    var now = new Date(), remaining = wait - (now - previous);
+                    context = this;
+                    args = arguments;
+                    if (remaining <= 0) {
+                        clearTimeout(timeout);
+                        timeout = null;
+                        previous = now;
+                        result = func.apply(context, args);
+                    } else if (!timeout) {
+                        timeout = setTimeout(later, remaining);
+                    }
+                    return result;
+                };
+            },
+            noop: function() {}
+        };
+    }();
+    var VERSION = "0.10.5";
+    var tokenizers = function() {
+        "use strict";
+        return {
+            nonword: nonword,
+            whitespace: whitespace,
+            obj: {
+                nonword: getObjTokenizer(nonword),
+                whitespace: getObjTokenizer(whitespace)
+            }
+        };
+        function whitespace(str) {
+            str = _.toStr(str);
+            return str ? str.split(/\s+/) : [];
+        }
+        function nonword(str) {
+            str = _.toStr(str);
+            return str ? str.split(/\W+/) : [];
+        }
+        function getObjTokenizer(tokenizer) {
+            return function setKey() {
+                var args = [].slice.call(arguments, 0);
+                return function tokenize(o) {
+                    var tokens = [];
+                    _.each(args, function(k) {
+                        tokens = tokens.concat(tokenizer(_.toStr(o[k])));
+                    });
+                    return tokens;
+                };
+            };
+        }
+    }();
+    var LruCache = function() {
+        "use strict";
+        function LruCache(maxSize) {
+            this.maxSize = _.isNumber(maxSize) ? maxSize : 100;
+            this.reset();
+            if (this.maxSize <= 0) {
+                this.set = this.get = $.noop;
+            }
+        }
+        _.mixin(LruCache.prototype, {
+            set: function set(key, val) {
+                var tailItem = this.list.tail, node;
+                if (this.size >= this.maxSize) {
+                    this.list.remove(tailItem);
+                    delete this.hash[tailItem.key];
+                }
+                if (node = this.hash[key]) {
+                    node.val = val;
+                    this.list.moveToFront(node);
+                } else {
+                    node = new Node(key, val);
+                    this.list.add(node);
+                    this.hash[key] = node;
+                    this.size++;
+                }
+            },
+            get: function get(key) {
+                var node = this.hash[key];
+                if (node) {
+                    this.list.moveToFront(node);
+                    return node.val;
+                }
+            },
+            reset: function reset() {
+                this.size = 0;
+                this.hash = {};
+                this.list = new List();
+            }
+        });
+        function List() {
+            this.head = this.tail = null;
+        }
+        _.mixin(List.prototype, {
+            add: function add(node) {
+                if (this.head) {
+                    node.next = this.head;
+                    this.head.prev = node;
+                }
+                this.head = node;
+                this.tail = this.tail || node;
+            },
+            remove: function remove(node) {
+                node.prev ? node.prev.next = node.next : this.head = node.next;
+                node.next ? node.next.prev = node.prev : this.tail = node.prev;
+            },
+            moveToFront: function(node) {
+                this.remove(node);
+                this.add(node);
+            }
+        });
+        function Node(key, val) {
+            this.key = key;
+            this.val = val;
+            this.prev = this.next = null;
+        }
+        return LruCache;
+    }();
+    var PersistentStorage = function() {
+        "use strict";
+        var ls, methods;
+        try {
+            ls = window.localStorage;
+            ls.setItem("~~~", "!");
+            ls.removeItem("~~~");
+        } catch (err) {
+            ls = null;
+        }
+        function PersistentStorage(namespace) {
+            this.prefix = [ "__", namespace, "__" ].join("");
+            this.ttlKey = "__ttl__";
+            this.keyMatcher = new RegExp("^" + 
_.escapeRegExChars(this.prefix));
+        }
+        if (ls && window.JSON) {
+            methods = {
+                _prefix: function(key) {
+                    return this.prefix + key;
+                },
+                _ttlKey: function(key) {
+                    return this._prefix(key) + this.ttlKey;
+                },
+                get: function(key) {
+                    if (this.isExpired(key)) {
+                        this.remove(key);
+                    }
+                    return decode(ls.getItem(this._prefix(key)));
+                },
+                set: function(key, val, ttl) {
+                    if (_.isNumber(ttl)) {
+                        ls.setItem(this._ttlKey(key), encode(now() + ttl));
+                    } else {
+                        ls.removeItem(this._ttlKey(key));
+                    }
+                    return ls.setItem(this._prefix(key), encode(val));
+                },
+                remove: function(key) {
+                    ls.removeItem(this._ttlKey(key));
+                    ls.removeItem(this._prefix(key));
+                    return this;
+                },
+                clear: function() {
+                    var i, key, keys = [], len = ls.length;
+                    for (i = 0; i < len; i++) {
+                        if ((key = ls.key(i)).match(this.keyMatcher)) {
+                            keys.push(key.replace(this.keyMatcher, ""));
+                        }
+                    }
+                    for (i = keys.length; i--; ) {
+                        this.remove(keys[i]);
+                    }
+                    return this;
+                },
+                isExpired: function(key) {
+                    var ttl = decode(ls.getItem(this._ttlKey(key)));
+                    return _.isNumber(ttl) && now() > ttl ? true : false;
+                }
+            };
+        } else {
+            methods = {
+                get: _.noop,
+                set: _.noop,
+                remove: _.noop,
+                clear: _.noop,
+                isExpired: _.noop
+            };
+        }
+        _.mixin(PersistentStorage.prototype, methods);
+        return PersistentStorage;
+        function now() {
+            return new Date().getTime();
+        }
+        function encode(val) {
+            return JSON.stringify(_.isUndefined(val) ? null : val);
+        }
+        function decode(val) {
+            return JSON.parse(val);
+        }
+    }();
+    var Transport = function() {
+        "use strict";
+        var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests 
= 6, sharedCache = new LruCache(10);
+        function Transport(o) {
+            o = o || {};
+            this.cancelled = false;
+            this.lastUrl = null;
+            this._send = o.transport ? callbackToDeferred(o.transport) : 
$.ajax;
+            this._get = o.rateLimiter ? o.rateLimiter(this._get) : this._get;
+            this._cache = o.cache === false ? new LruCache(0) : sharedCache;
+        }
+        Transport.setMaxPendingRequests = function setMaxPendingRequests(num) {
+            maxPendingRequests = num;
+        };
+        Transport.resetCache = function resetCache() {
+            sharedCache.reset();
+        };
+        _.mixin(Transport.prototype, {
+            _get: function(url, o, cb) {
+                var that = this, jqXhr;
+                if (this.cancelled || url !== this.lastUrl) {
+                    return;
+                }
+                if (jqXhr = pendingRequests[url]) {
+                    jqXhr.done(done).fail(fail);
+                } else if (pendingRequestsCount < maxPendingRequests) {
+                    pendingRequestsCount++;
+                    pendingRequests[url] = this._send(url, 
o).done(done).fail(fail).always(always);
+                } else {
+                    this.onDeckRequestArgs = [].slice.call(arguments, 0);
+                }
+                function done(resp) {
+                    cb && cb(null, resp);
+                    that._cache.set(url, resp);
+                }
+                function fail() {
+                    cb && cb(true);
+                }
+                function always() {
+                    pendingRequestsCount--;
+                    delete pendingRequests[url];
+                    if (that.onDeckRequestArgs) {
+                        that._get.apply(that, that.onDeckRequestArgs);
+                        that.onDeckRequestArgs = null;
+                    }
+                }
+            },
+            get: function(url, o, cb) {
+                var resp;
+                if (_.isFunction(o)) {
+                    cb = o;
+                    o = {};
+                }
+                this.cancelled = false;
+                this.lastUrl = url;
+                if (resp = this._cache.get(url)) {
+                    _.defer(function() {
+                        cb && cb(null, resp);
+                    });
+                } else {
+                    this._get(url, o, cb);
+                }
+                return !!resp;
+            },
+            cancel: function() {
+                this.cancelled = true;
+            }
+        });
+        return Transport;
+        function callbackToDeferred(fn) {
+            return function customSendWrapper(url, o) {
+                var deferred = $.Deferred();
+                fn(url, o, onSuccess, onError);
+                return deferred;
+                function onSuccess(resp) {
+                    _.defer(function() {
+                        deferred.resolve(resp);
+                    });
+                }
+                function onError(err) {
+                    _.defer(function() {
+                        deferred.reject(err);
+                    });
+                }
+            };
+        }
+    }();
+    var SearchIndex = function() {
+        "use strict";
+        function SearchIndex(o) {
+            o = o || {};
+            if (!o.datumTokenizer || !o.queryTokenizer) {
+                $.error("datumTokenizer and queryTokenizer are both required");
+            }
+            this.datumTokenizer = o.datumTokenizer;
+            this.queryTokenizer = o.queryTokenizer;
+            this.reset();
+        }
+        _.mixin(SearchIndex.prototype, {
+            bootstrap: function bootstrap(o) {
+                this.datums = o.datums;
+                this.trie = o.trie;
+            },
+            add: function(data) {
+                var that = this;
+                data = _.isArray(data) ? data : [ data ];
+                _.each(data, function(datum) {
+                    var id, tokens;
+                    id = that.datums.push(datum) - 1;
+                    tokens = normalizeTokens(that.datumTokenizer(datum));
+                    _.each(tokens, function(token) {
+                        var node, chars, ch;
+                        node = that.trie;
+                        chars = token.split("");
+                        while (ch = chars.shift()) {
+                            node = node.children[ch] || (node.children[ch] = 
newNode());
+                            node.ids.push(id);
+                        }
+                    });
+                });
+            },
+            get: function get(query) {
+                var that = this, tokens, matches;
+                tokens = normalizeTokens(this.queryTokenizer(query));
+                _.each(tokens, function(token) {
+                    var node, chars, ch, ids;
+                    if (matches && matches.length === 0) {
+                        return false;
+                    }
+                    node = that.trie;
+                    chars = token.split("");
+                    while (node && (ch = chars.shift())) {
+                        node = node.children[ch];
+                    }
+                    if (node && chars.length === 0) {
+                        ids = node.ids.slice(0);
+                        matches = matches ? getIntersection(matches, ids) : 
ids;
+                    } else {
+                        matches = [];
+                        return false;
+                    }
+                });
+                return matches ? _.map(unique(matches), function(id) {
+                    return that.datums[id];
+                }) : [];
+            },
+            reset: function reset() {
+                this.datums = [];
+                this.trie = newNode();
+            },
+            serialize: function serialize() {
+                return {
+                    datums: this.datums,
+                    trie: this.trie
+                };
+            }
+        });
+        return SearchIndex;
+        function normalizeTokens(tokens) {
+            tokens = _.filter(tokens, function(token) {
+                return !!token;
+            });
+            tokens = _.map(tokens, function(token) {
+                return token.toLowerCase();
+            });
+            return tokens;
+        }
+        function newNode() {
+            return {
+                ids: [],
+                children: {}
+            };
+        }
+        function unique(array) {
+            var seen = {}, uniques = [];
+            for (var i = 0, len = array.length; i < len; i++) {
+                if (!seen[array[i]]) {
+                    seen[array[i]] = true;
+                    uniques.push(array[i]);
+                }
+            }
+            return uniques;
+        }
+        function getIntersection(arrayA, arrayB) {
+            var ai = 0, bi = 0, intersection = [];
+            arrayA = arrayA.sort(compare);
+            arrayB = arrayB.sort(compare);
+            var lenArrayA = arrayA.length, lenArrayB = arrayB.length;
+            while (ai < lenArrayA && bi < lenArrayB) {
+                if (arrayA[ai] < arrayB[bi]) {
+                    ai++;
+                } else if (arrayA[ai] > arrayB[bi]) {
+                    bi++;
+                } else {
+                    intersection.push(arrayA[ai]);
+                    ai++;
+                    bi++;
+                }
+            }
+            return intersection;
+            function compare(a, b) {
+                return a - b;
+            }
+        }
+    }();
+    var oParser = function() {
+        "use strict";
+        return {
+            local: getLocal,
+            prefetch: getPrefetch,
+            remote: getRemote
+        };
+        function getLocal(o) {
+            return o.local || null;
+        }
+        function getPrefetch(o) {
+            var prefetch, defaults;
+            defaults = {
+                url: null,
+                thumbprint: "",
+                ttl: 24 * 60 * 60 * 1e3,
+                filter: null,
+                ajax: {}
+            };
+            if (prefetch = o.prefetch || null) {
+                prefetch = _.isString(prefetch) ? {
+                    url: prefetch
+                } : prefetch;
+                prefetch = _.mixin(defaults, prefetch);
+                prefetch.thumbprint = VERSION + prefetch.thumbprint;
+                prefetch.ajax.type = prefetch.ajax.type || "GET";
+                prefetch.ajax.dataType = prefetch.ajax.dataType || "json";
+                !prefetch.url && $.error("prefetch requires url to be set");
+            }
+            return prefetch;
+        }
+        function getRemote(o) {
+            var remote, defaults;
+            defaults = {
+                url: null,
+                cache: true,
+                wildcard: "%QUERY",
+                replace: null,
+                rateLimitBy: "debounce",
+                rateLimitWait: 300,
+                send: null,
+                filter: null,
+                ajax: {}
+            };
+            if (remote = o.remote || null) {
+                remote = _.isString(remote) ? {
+                    url: remote
+                } : remote;
+                remote = _.mixin(defaults, remote);
+                remote.rateLimiter = /^throttle$/i.test(remote.rateLimitBy) ? 
byThrottle(remote.rateLimitWait) : byDebounce(remote.rateLimitWait);
+                remote.ajax.type = remote.ajax.type || "GET";
+                remote.ajax.dataType = remote.ajax.dataType || "json";
+                delete remote.rateLimitBy;
+                delete remote.rateLimitWait;
+                !remote.url && $.error("remote requires url to be set");
+            }
+            return remote;
+            function byDebounce(wait) {
+                return function(fn) {
+                    return _.debounce(fn, wait);
+                };
+            }
+            function byThrottle(wait) {
+                return function(fn) {
+                    return _.throttle(fn, wait);
+                };
+            }
+        }
+    }();
+    (function(root) {
+        "use strict";
+        var old, keys;
+        old = root.Bloodhound;
+        keys = {
+            data: "data",
+            protocol: "protocol",
+            thumbprint: "thumbprint"
+        };
+        root.Bloodhound = Bloodhound;
+        function Bloodhound(o) {
+            if (!o || !o.local && !o.prefetch && !o.remote) {
+                $.error("one of local, prefetch, or remote is required");
+            }
+            this.limit = o.limit || 5;
+            this.sorter = getSorter(o.sorter);
+            this.dupDetector = o.dupDetector || ignoreDuplicates;
+            this.local = oParser.local(o);
+            this.prefetch = oParser.prefetch(o);
+            this.remote = oParser.remote(o);
+            this.cacheKey = this.prefetch ? this.prefetch.cacheKey || 
this.prefetch.url : null;
+            this.index = new SearchIndex({
+                datumTokenizer: o.datumTokenizer,
+                queryTokenizer: o.queryTokenizer
+            });
+            this.storage = this.cacheKey ? new 
PersistentStorage(this.cacheKey) : null;
+        }
+        Bloodhound.noConflict = function noConflict() {
+            root.Bloodhound = old;
+            return Bloodhound;
+        };
+        Bloodhound.tokenizers = tokenizers;
+        _.mixin(Bloodhound.prototype, {
+            _loadPrefetch: function loadPrefetch(o) {
+                var that = this, serialized, deferred;
+                if (serialized = this._readFromStorage(o.thumbprint)) {
+                    this.index.bootstrap(serialized);
+                    deferred = $.Deferred().resolve();
+                } else {
+                    deferred = $.ajax(o.url, 
o.ajax).done(handlePrefetchResponse);
+                }
+                return deferred;
+                function handlePrefetchResponse(resp) {
+                    that.clear();
+                    that.add(o.filter ? o.filter(resp) : resp);
+                    that._saveToStorage(that.index.serialize(), o.thumbprint, 
o.ttl);
+                }
+            },
+            _getFromRemote: function getFromRemote(query, cb) {
+                var that = this, url, uriEncodedQuery;
+                if (!this.transport) {
+                    return;
+                }
+                query = query || "";
+                uriEncodedQuery = encodeURIComponent(query);
+                url = this.remote.replace ? 
this.remote.replace(this.remote.url, query) : 
this.remote.url.replace(this.remote.wildcard, uriEncodedQuery);
+                return this.transport.get(url, this.remote.ajax, 
handleRemoteResponse);
+                function handleRemoteResponse(err, resp) {
+                    err ? cb([]) : cb(that.remote.filter ? 
that.remote.filter(resp) : resp);
+                }
+            },
+            _cancelLastRemoteRequest: function cancelLastRemoteRequest() {
+                this.transport && this.transport.cancel();
+            },
+            _saveToStorage: function saveToStorage(data, thumbprint, ttl) {
+                if (this.storage) {
+                    this.storage.set(keys.data, data, ttl);
+                    this.storage.set(keys.protocol, location.protocol, ttl);
+                    this.storage.set(keys.thumbprint, thumbprint, ttl);
+                }
+            },
+            _readFromStorage: function readFromStorage(thumbprint) {
+                var stored = {}, isExpired;
+                if (this.storage) {
+                    stored.data = this.storage.get(keys.data);
+                    stored.protocol = this.storage.get(keys.protocol);
+                    stored.thumbprint = this.storage.get(keys.thumbprint);
+                }
+                isExpired = stored.thumbprint !== thumbprint || 
stored.protocol !== location.protocol;
+                return stored.data && !isExpired ? stored.data : null;
+            },
+            _initialize: function initialize() {
+                var that = this, local = this.local, deferred;
+                deferred = this.prefetch ? this._loadPrefetch(this.prefetch) : 
$.Deferred().resolve();
+                local && deferred.done(addLocalToIndex);
+                this.transport = this.remote ? new Transport(this.remote) : 
null;
+                return this.initPromise = deferred.promise();
+                function addLocalToIndex() {
+                    that.add(_.isFunction(local) ? local() : local);
+                }
+            },
+            initialize: function initialize(force) {
+                return !this.initPromise || force ? this._initialize() : 
this.initPromise;
+            },
+            add: function add(data) {
+                this.index.add(data);
+            },
+            get: function get(query, cb) {
+                var that = this, matches = [], cacheHit = false;
+                matches = this.index.get(query);
+                matches = this.sorter(matches).slice(0, this.limit);
+                matches.length < this.limit ? cacheHit = 
this._getFromRemote(query, returnRemoteMatches) : 
this._cancelLastRemoteRequest();
+                if (!cacheHit) {
+                    (matches.length > 0 || !this.transport) && cb && 
cb(matches);
+                }
+                function returnRemoteMatches(remoteMatches) {
+                    var matchesWithBackfill = matches.slice(0);
+                    _.each(remoteMatches, function(remoteMatch) {
+                        var isDuplicate;
+                        isDuplicate = _.some(matchesWithBackfill, 
function(match) {
+                            return that.dupDetector(remoteMatch, match);
+                        });
+                        !isDuplicate && matchesWithBackfill.push(remoteMatch);
+                        return matchesWithBackfill.length < that.limit;
+                    });
+                    cb && cb(that.sorter(matchesWithBackfill));
+                }
+            },
+            clear: function clear() {
+                this.index.reset();
+            },
+            clearPrefetchCache: function clearPrefetchCache() {
+                this.storage && this.storage.clear();
+            },
+            clearRemoteCache: function clearRemoteCache() {
+                this.transport && Transport.resetCache();
+            },
+            ttAdapter: function ttAdapter() {
+                return _.bind(this.get, this);
+            }
+        });
+        return Bloodhound;
+        function getSorter(sortFn) {
+            return _.isFunction(sortFn) ? sort : noSort;
+            function sort(array) {
+                return array.sort(sortFn);
+            }
+            function noSort(array) {
+                return array;
+            }
+        }
+        function ignoreDuplicates() {
+            return false;
+        }
+    })(this);
+})(window.jQuery);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/461ac9f1/usage/cli/src/main/resources/brooklyn/item-lister/statics/style/js/underscore-min.js
----------------------------------------------------------------------
diff --git 
a/usage/cli/src/main/resources/brooklyn/item-lister/statics/style/js/underscore-min.js
 
b/usage/cli/src/main/resources/brooklyn/item-lister/statics/style/js/underscore-min.js
new file mode 100644
index 0000000..11f1d96
--- /dev/null
+++ 
b/usage/cli/src/main/resources/brooklyn/item-lister/statics/style/js/underscore-min.js
@@ -0,0 +1,6 @@
+//     Underscore.js 1.7.0
+//     http://underscorejs.org
+//     (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative 
Reporters & Editors
+//     Underscore may be freely distributed under the MIT license.
+(function(){var 
n=this,t=n._,r=Array.prototype,e=Object.prototype,u=Function.prototype,i=r.push,a=r.slice,o=r.concat,l=e.toString,c=e.hasOwnProperty,f=Array.isArray,s=Object.keys,p=u.bind,h=function(n){return
 n instanceof h?n:this instanceof h?void(this._wrapped=n):new 
h(n)};"undefined"!=typeof exports?("undefined"!=typeof 
module&&module.exports&&(exports=module.exports=h),exports._=h):n._=h,h.VERSION="1.7.0";var
 g=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return 
function(r){return n.call(t,r)};case 2:return function(r,e){return 
n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 
4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return 
n.apply(t,arguments)}};h.iteratee=function(n,t,r){return 
null==n?h.identity:h.isFunction(n)?g(n,t,r):h.isObject(n)?h.matches(n):h.property(n)},h.each=h.forEach=function(n,t,r){if(null==n)return
 n;t=g(t,r);var e,u=n.length;if(u===+u)for(e=0;u>e;e++)t(n[e],e,n);else{var 
i=h.keys(n);for(e
 =0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return 
n},h.map=h.collect=function(n,t,r){if(null==n)return[];t=h.iteratee(t,r);for(var
 
e,u=n.length!==+n.length&&h.keys(n),i=(u||n).length,a=Array(i),o=0;i>o;o++)e=u?u[o]:o,a[o]=t(n[e],e,n);return
 a};var v="Reduce of empty array with no initial 
value";h.reduce=h.foldl=h.inject=function(n,t,r,e){null==n&&(n=[]),t=g(t,e,4);var
 
u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length,o=0;if(arguments.length<3){if(!a)throw
 new 
TypeError(v);r=n[i?i[o++]:o++]}for(;a>o;o++)u=i?i[o]:o,r=t(r,n[u],u,n);return 
r},h.reduceRight=h.foldr=function(n,t,r,e){null==n&&(n=[]),t=g(t,e,4);var 
u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;if(arguments.length<3){if(!a)throw
 new TypeError(v);r=n[i?i[--a]:--a]}for(;a--;)u=i?i[a]:a,r=t(r,n[u],u,n);return 
r},h.find=h.detect=function(n,t,r){var e;return 
t=h.iteratee(t,r),h.some(n,function(n,r,u){return t(n,r,u)?(e=n,!0):void 
0}),e},h.filter=h.select=function(n,t,r){var e=[];return 
null==n?e:(t=h.iteratee(t,r),h.each(n,
 function(n,r,u){t(n,r,u)&&e.push(n)}),e)},h.reject=function(n,t,r){return 
h.filter(n,h.negate(h.iteratee(t)),r)},h.every=h.all=function(n,t,r){if(null==n)return!0;t=h.iteratee(t,r);var
 
e,u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;for(e=0;a>e;e++)if(u=i?i[e]:e,!t(n[u],u,n))return!1;return!0},h.some=h.any=function(n,t,r){if(null==n)return!1;t=h.iteratee(t,r);var
 
e,u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;for(e=0;a>e;e++)if(u=i?i[e]:e,t(n[u],u,n))return!0;return!1},h.contains=h.include=function(n,t){return
 
null==n?!1:(n.length!==+n.length&&(n=h.values(n)),h.indexOf(n,t)>=0)},h.invoke=function(n,t){var
 r=a.call(arguments,2),e=h.isFunction(t);return 
h.map(n,function(n){return(e?t:n[t]).apply(n,r)})},h.pluck=function(n,t){return 
h.map(n,h.property(t))},h.where=function(n,t){return 
h.filter(n,h.matches(t))},h.findWhere=function(n,t){return 
h.find(n,h.matches(t))},h.max=function(n,t,r){var 
e,u,i=-1/0,a=-1/0;if(null==t&&null!=n){n=n.length===+n.length?n:h.values(n);for(va
 r o=0,l=n.length;l>o;o++)e=n[o],e>i&&(i=e)}else 
t=h.iteratee(t,r),h.each(n,function(n,r,e){u=t(n,r,e),(u>a||u===-1/0&&i===-1/0)&&(i=n,a=u)});return
 i},h.min=function(n,t,r){var 
e,u,i=1/0,a=1/0;if(null==t&&null!=n){n=n.length===+n.length?n:h.values(n);for(var
 o=0,l=n.length;l>o;o++)e=n[o],i>e&&(i=e)}else 
t=h.iteratee(t,r),h.each(n,function(n,r,e){u=t(n,r,e),(a>u||1/0===u&&1/0===i)&&(i=n,a=u)});return
 i},h.shuffle=function(n){for(var 
t,r=n&&n.length===+n.length?n:h.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=h.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return
 u},h.sample=function(n,t,r){return 
null==t||r?(n.length!==+n.length&&(n=h.values(n)),n[h.random(n.length-1)]):h.shuffle(n).slice(0,Math.max(0,t))},h.sortBy=function(n,t,r){return
 
t=h.iteratee(t,r),h.pluck(h.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var
 r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 
1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var m=func
 tion(n){return function(t,r,e){var u={};return 
r=h.iteratee(r,e),h.each(t,function(e,i){var 
a=r(e,i,t);n(u,e,a)}),u}};h.groupBy=m(function(n,t,r){h.has(n,r)?n[r].push(t):n[r]=[t]}),h.indexBy=m(function(n,t,r){n[r]=t}),h.countBy=m(function(n,t,r){h.has(n,r)?n[r]++:n[r]=1}),h.sortedIndex=function(n,t,r,e){r=h.iteratee(r,e,1);for(var
 u=r(t),i=0,a=n.length;a>i;){var o=i+a>>>1;r(n[o])<u?i=o+1:a=o}return 
i},h.toArray=function(n){return 
n?h.isArray(n)?a.call(n):n.length===+n.length?h.map(n,h.identity):h.values(n):[]},h.size=function(n){return
 
null==n?0:n.length===+n.length?n.length:h.keys(n).length},h.partition=function(n,t,r){t=h.iteratee(t,r);var
 e=[],u=[];return 
h.each(n,function(n,r,i){(t(n,r,i)?e:u).push(n)}),[e,u]},h.first=h.head=h.take=function(n,t,r){return
 null==n?void 
0:null==t||r?n[0]:0>t?[]:a.call(n,0,t)},h.initial=function(n,t,r){return 
a.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},h.last=function(n,t,r){return
 null==n?void 0:null==t||r?n[n.length-1]:a.call(n,Math.max(n.l
 ength-t,0))},h.rest=h.tail=h.drop=function(n,t,r){return 
a.call(n,null==t||r?1:t)},h.compact=function(n){return 
h.filter(n,h.identity)};var 
y=function(n,t,r,e){if(t&&h.every(n,h.isArray))return o.apply(e,n);for(var 
u=0,a=n.length;a>u;u++){var 
l=n[u];h.isArray(l)||h.isArguments(l)?t?i.apply(e,l):y(l,t,r,e):r||e.push(l)}return
 e};h.flatten=function(n,t){return y(n,t,!1,[])},h.without=function(n){return 
h.difference(n,a.call(arguments,1))},h.uniq=h.unique=function(n,t,r,e){if(null==n)return[];h.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=h.iteratee(r,e));for(var
 u=[],i=[],a=0,o=n.length;o>a;a++){var l=n[a];if(t)a&&i===l||u.push(l),i=l;else 
if(r){var c=r(l,a,n);h.indexOf(i,c)<0&&(i.push(c),u.push(l))}else 
h.indexOf(u,l)<0&&u.push(l)}return u},h.union=function(){return 
h.uniq(y(arguments,!0,!0,[]))},h.intersection=function(n){if(null==n)return[];for(var
 t=[],r=arguments.length,e=0,u=n.length;u>e;e++){var 
i=n[e];if(!h.contains(t,i)){for(var 
a=1;r>a&&h.contains(arguments[a],i);a++);a===r&&t.p
 ush(i)}}return t},h.difference=function(n){var 
t=y(a.call(arguments,1),!0,!0,[]);return 
h.filter(n,function(n){return!h.contains(t,n)})},h.zip=function(n){if(null==n)return[];for(var
 
t=h.max(arguments,"length").length,r=Array(t),e=0;t>e;e++)r[e]=h.pluck(arguments,e);return
 r},h.object=function(n,t){if(null==n)return{};for(var 
r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return 
r},h.indexOf=function(n,t,r){if(null==n)return-1;var 
e=0,u=n.length;if(r){if("number"!=typeof r)return 
e=h.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}for(;u>e;e++)if(n[e]===t)return
 e;return-1},h.lastIndexOf=function(n,t,r){if(null==n)return-1;var 
e=n.length;for("number"==typeof 
r&&(e=0>r?e+r+1:Math.min(e,r+1));--e>=0;)if(n[e]===t)return 
e;return-1},h.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=r||1;for(var
 e=Math.max(Math.ceil((t-n)/r),0),u=Array(e),i=0;e>i;i++,n+=r)u[i]=n;return 
u};var d=function(){};h.bind=function(n,t){var r,e;if(p&&n.bind===p)return 
p.apply(n,a.c
 all(arguments,1));if(!h.isFunction(n))throw new TypeError("Bind must be called 
on a function");return r=a.call(arguments,2),e=function(){if(!(this instanceof 
e))return n.apply(t,r.concat(a.call(arguments)));d.prototype=n.prototype;var 
u=new d;d.prototype=null;var i=n.apply(u,r.concat(a.call(arguments)));return 
h.isObject(i)?i:u}},h.partial=function(n){var t=a.call(arguments,1);return 
function(){for(var 
r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===h&&(e[u]=arguments[r++]);for(;r<arguments.length;)e.push(arguments[r++]);return
 n.apply(this,e)}},h.bindAll=function(n){var 
t,r,e=arguments.length;if(1>=e)throw new Error("bindAll must be passed function 
names");for(t=1;e>t;t++)r=arguments[t],n[r]=h.bind(n[r],n);return 
n},h.memoize=function(n,t){var r=function(e){var 
u=r.cache,i=t?t.apply(this,arguments):e;return 
h.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return 
r.cache={},r},h.delay=function(n,t){var r=a.call(arguments,2);return 
setTimeout(function(){return n.apply(null,r)},t)},h.de
 fer=function(n){return 
h.delay.apply(h,[n,1].concat(a.call(arguments,1)))},h.throttle=function(n,t,r){var
 e,u,i,a=null,o=0;r||(r={});var 
l=function(){o=r.leading===!1?0:h.now(),a=null,i=n.apply(e,u),a||(e=u=null)};return
 function(){var c=h.now();o||r.leading!==!1||(o=c);var f=t-(c-o);return 
e=this,u=arguments,0>=f||f>t?(clearTimeout(a),a=null,o=c,i=n.apply(e,u),a||(e=u=null)):a||r.trailing===!1||(a=setTimeout(l,f)),i}},h.debounce=function(n,t,r){var
 e,u,i,a,o,l=function(){var 
c=h.now()-a;t>c&&c>0?e=setTimeout(l,t-c):(e=null,r||(o=n.apply(i,u),e||(i=u=null)))};return
 function(){i=this,u=arguments,a=h.now();var c=r&&!e;return 
e||(e=setTimeout(l,t)),c&&(o=n.apply(i,u),i=u=null),o}},h.wrap=function(n,t){return
 h.partial(t,n)},h.negate=function(n){return 
function(){return!n.apply(this,arguments)}},h.compose=function(){var 
n=arguments,t=n.length-1;return function(){for(var 
r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return 
e}},h.after=function(n,t){return function(){return--
 n<1?t.apply(this,arguments):void 0}},h.before=function(n,t){var r;return 
function(){return--n>0?r=t.apply(this,arguments):t=null,r}},h.once=h.partial(h.before,2),h.keys=function(n){if(!h.isObject(n))return[];if(s)return
 s(n);var t=[];for(var r in n)h.has(n,r)&&t.push(r);return 
t},h.values=function(n){for(var 
t=h.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return 
e},h.pairs=function(n){for(var 
t=h.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return 
e},h.invert=function(n){for(var 
t={},r=h.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return 
t},h.functions=h.methods=function(n){var t=[];for(var r in 
n)h.isFunction(n[r])&&t.push(r);return 
t.sort()},h.extend=function(n){if(!h.isObject(n))return n;for(var 
t,r,e=1,u=arguments.length;u>e;e++){t=arguments[e];for(r in 
t)c.call(t,r)&&(n[r]=t[r])}return n},h.pick=function(n,t,r){var 
e,u={};if(null==n)return u;if(h.isFunction(t)){t=g(t,r);for(e in n){var 
i=n[e];t(i,e,n)&&(u[e]=i)}}else{var l=o.apply([],a.call(argume
 nts,1));n=new Object(n);for(var c=0,f=l.length;f>c;c++)e=l[c],e in 
n&&(u[e]=n[e])}return 
u},h.omit=function(n,t,r){if(h.isFunction(t))t=h.negate(t);else{var 
e=h.map(o.apply([],a.call(arguments,1)),String);t=function(n,t){return!h.contains(e,t)}}return
 h.pick(n,t,r)},h.defaults=function(n){if(!h.isObject(n))return n;for(var 
t=1,r=arguments.length;r>t;t++){var e=arguments[t];for(var u in e)n[u]===void 
0&&(n[u]=e[u])}return n},h.clone=function(n){return 
h.isObject(n)?h.isArray(n)?n.slice():h.extend({},n):n},h.tap=function(n,t){return
 t(n),n};var b=function(n,t,r,e){if(n===t)return 
0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof 
h&&(n=n._wrapped),t instanceof h&&(t=t._wrapped);var 
u=l.call(n);if(u!==l.call(t))return!1;switch(u){case"[object 
RegExp]":case"[object String]":return""+n==""+t;case"[object 
Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object 
Date]":case"[object Boolean]":return+n===+t}if("object"!=typeof 
n||"object"!=typeof t)return!1;for(var i
 =r.length;i--;)if(r[i]===n)return e[i]===t;var 
a=n.constructor,o=t.constructor;if(a!==o&&"constructor"in n&&"constructor"in 
t&&!(h.isFunction(a)&&a instanceof a&&h.isFunction(o)&&o instanceof 
o))return!1;r.push(n),e.push(t);var c,f;if("[object 
Array]"===u){if(c=n.length,f=c===t.length)for(;c--&&(f=b(n[c],t[c],r,e)););}else{var
 
s,p=h.keys(n);if(c=p.length,f=h.keys(t).length===c)for(;c--&&(s=p[c],f=h.has(t,s)&&b(n[s],t[s],r,e)););}return
 r.pop(),e.pop(),f};h.isEqual=function(n,t){return 
b(n,t,[],[])},h.isEmpty=function(n){if(null==n)return!0;if(h.isArray(n)||h.isString(n)||h.isArguments(n))return
 0===n.length;for(var t in 
n)if(h.has(n,t))return!1;return!0},h.isElement=function(n){return!(!n||1!==n.nodeType)},h.isArray=f||function(n){return"[object
 Array]"===l.call(n)},h.isObject=function(n){var t=typeof 
n;return"function"===t||"object"===t&&!!n},h.each(["Arguments","Function","String","Number","Date","RegExp"],function(n){h["is"+n]=function(t){return
 l.call(t)==="[object "+n+"]"}}),h.
 isArguments(arguments)||(h.isArguments=function(n){return 
h.has(n,"callee")}),"function"!=typeof/./&&(h.isFunction=function(n){return"function"==typeof
 n||!1}),h.isFinite=function(n){return 
isFinite(n)&&!isNaN(parseFloat(n))},h.isNaN=function(n){return 
h.isNumber(n)&&n!==+n},h.isBoolean=function(n){return n===!0||n===!1||"[object 
Boolean]"===l.call(n)},h.isNull=function(n){return 
null===n},h.isUndefined=function(n){return n===void 
0},h.has=function(n,t){return 
null!=n&&c.call(n,t)},h.noConflict=function(){return 
n._=t,this},h.identity=function(n){return n},h.constant=function(n){return 
function(){return n}},h.noop=function(){},h.property=function(n){return 
function(t){return t[n]}},h.matches=function(n){var 
t=h.pairs(n),r=t.length;return function(n){if(null==n)return!r;n=new 
Object(n);for(var e=0;r>e;e++){var u=t[e],i=u[0];if(u[1]!==n[i]||!(i in 
n))return!1}return!0}},h.times=function(n,t,r){var 
e=Array(Math.max(0,n));t=g(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return 
e},h.random=funct
 ion(n,t){return 
null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},h.now=Date.now||function(){return(new
 Date).getTime()};var 
_={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","`":"&#x60;"},w=h.invert(_),j=function(n){var
 t=function(t){return 
n[t]},r="(?:"+h.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return 
function(n){return 
n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};h.escape=j(_),h.unescape=j(w),h.result=function(n,t){if(null==n)return
 void 0;var r=n[t];return h.isFunction(r)?n[t]():r};var 
x=0;h.uniqueId=function(n){var t=++x+"";return 
n?n+t:t},h.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var
 
A=/(.)^/,k={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},O=/\\|'|\r|\n|\u2028|\u2029/g,F=function(n){return"\\"+k[n]};h.template=function(n,t,r){!t&&r&&(t=r),t=h.defaults({},t,h.templateSettings);var
 
e=RegExp([(t.escape||A).source,(t.interpolate||A).source,(t.evaluate||A).source]
 .join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,a,o){return 
i+=n.slice(u,o).replace(O,F),u=o+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":a&&(i+="';\n"+a+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var
 
__t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return
 __p;\n";try{var a=new Function(t.variable||"obj","_",i)}catch(o){throw 
o.source=i,o}var l=function(n){return 
a.call(this,n,h)},c=t.variable||"obj";return 
l.source="function("+c+"){\n"+i+"}",l},h.chain=function(n){var t=h(n);return 
t._chain=!0,t};var E=function(n){return 
this._chain?h(n).chain():n};h.mixin=function(n){h.each(h.functions(n),function(t){var
 r=h[t]=n[t];h.prototype[t]=function(){var n=[this._wrapped];return 
i.apply(n,arguments),E.call(this,r.apply(h,n))}})},h.mixin(h),h.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var
 t=r[n];h.prototype[n]=
 function(){var r=this._wrapped;return 
t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete 
r[0],E.call(this,r)}}),h.each(["concat","join","slice"],function(n){var 
t=r[n];h.prototype[n]=function(){return 
E.call(this,t.apply(this._wrapped,arguments))}}),h.prototype.value=function(){return
 this._wrapped},"function"==typeof 
define&&define.amd&&define("underscore",[],function(){return h})}).call(this);
+//# sourceMappingURL=underscore-min.map
\ No newline at end of file

Reply via email to