diff --git a/.gitignore b/.gitignore
index 20742656b914e4af764599873a85644e5b2a4ce2..7bcf3a845be62cfeea00432a89238f691a725877 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,4 @@ gradle.properties
 .project
 .settings/
 InstanceSettings.xml
+xnat-conf.properties
diff --git a/src/main/webapp/WEB-INF/tags/page/head.tag b/src/main/webapp/WEB-INF/tags/page/head.tag
old mode 100644
new mode 100755
index 34715dd843fe1786d9a042128931b0d1c68452d0..884061eed545badf2c7611fb254e686e7d8faf24
--- a/src/main/webapp/WEB-INF/tags/page/head.tag
+++ b/src/main/webapp/WEB-INF/tags/page/head.tag
@@ -3,8 +3,8 @@
 <%@ taglib prefix="pg" tagdir="/WEB-INF/tags/page" %>
 
 <%@ attribute name="title" %>
-<%@ attribute name="headTop" %>
-<%@ attribute name="headBottom" %>
+<%@ attribute name="headTop" fragment="true" %>
+<%@ attribute name="headBottom" fragment="true" %>
 
 <head>
 
@@ -12,32 +12,13 @@
 
     <title>${title}</title>
 
-    <script>
-        var PAGE = {};
-        PAGE.siteRoot = '${sessionScope.siteRoot}';
-        PAGE.themeName = '${sessionScope.themeName}';
-        PAGE.themeRoot = '${sessionScope.themeRoot}';
-        PAGE.pageRoot = '${sessionScope.pageRoot}';
-        PAGE.username = '${sessionScope.username}';
-        PAGE.isDialog = '${sessionScope.isDialog}';
-        PAGE.isModal = '${sessionScope.isModal}';
-        PAGE.session = {
-            user: '${sessionScope.username}',
-            token: '${sessionScope.csrfToken}',
-            expiration: '${sessionScope.sessionExpiration}'
-        };
-        PAGE.session.startTime = ('${sessionScope.sessionExpiration}'.split(',')[0]) * 1;
-        PAGE.session.duration = ('${sessionScope.sessionExpiration}'.split(',')[1]) * 1;
-        PAGE.session.timeout = PAGE.session.startTime + PAGE.session.duration;
-
-        //    PAGE.session.timer = PAGE.session.timeout - 1000;
-        //    setInterval(function(){
-        //        PAGE.session.timer -= 1000;
-        //        console.log(parseInt(PAGE.session.timer/1000));
-        //    }, 1000);
-
-        console.log(PAGE);
-    </script>
+        <c:if test="${empty hasInit}">
+            <pg:init>
+                <c:if test="${empty hasVars}">
+                    <pg:jsvars/>
+                </c:if>
+            </pg:init>
+        </c:if>
 
     <c:set var="rootUrl" value="${sessionScope.themeRoot}"/>
 
diff --git a/src/main/webapp/WEB-INF/tags/page/init.tag b/src/main/webapp/WEB-INF/tags/page/init.tag
old mode 100644
new mode 100755
index bd1534d6babc71b6399a7c861d7c58dcff818277..db8bfa8425ce888111cf7c0138c4837507aa2411
--- a/src/main/webapp/WEB-INF/tags/page/init.tag
+++ b/src/main/webapp/WEB-INF/tags/page/init.tag
@@ -21,11 +21,11 @@
 <c:set var="themeName" value="${cookie.THEME_NAME.value}" scope="session"/>
 
 <%-- if there's a theme specified in the request, use that --%>
-<c:if test="${not empty param.theme}">
+<c:if test="${empty themeName && not empty param.theme}">
     <c:set var="themeName" value="${param.theme}" scope="session"/>
 </c:if>
 
-<%-- change 'siteRoot' to the root of your web app --%>
+<%-- set 'siteRoot' to the root of your web app --%>
 <c:set var="siteRoot" value="${pageContext.request.contextPath}" scope="session"/>
 
 <%-- add a leading slash if siteRoot is not empty and doesn't already start with a slash --%>
@@ -58,3 +58,6 @@
 <c:set var="isDialog" value="${not empty param.dialog && param.dialog == 'true'}"/>
 
 <c:set var="hasInit" value="true" scope="request"/>
+
+<%-- inject content on init--%>
+<jsp:doBody/>
diff --git a/src/main/webapp/WEB-INF/tags/page/jsvars.tag b/src/main/webapp/WEB-INF/tags/page/jsvars.tag
new file mode 100755
index 0000000000000000000000000000000000000000..d6eca72c6c52bd26677561051893667f0119e339
--- /dev/null
+++ b/src/main/webapp/WEB-INF/tags/page/jsvars.tag
@@ -0,0 +1,27 @@
+<%@ tag description="Initialize Variables" pageEncoding="UTF-8" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+
+<script>
+    var PAGE = {};
+    PAGE.siteRoot = '${sessionScope.siteRoot}';
+    PAGE.themeName = '${sessionScope.themeName}';
+    PAGE.themeRoot = '${sessionScope.themeRoot}';
+    PAGE.pageRoot = '${sessionScope.pageRoot}';
+    PAGE.username = '${sessionScope.username}';
+    PAGE.isDialog = '${sessionScope.isDialog}';
+    PAGE.isModal = '${sessionScope.isModal}';
+    PAGE.session = {
+        username: '${sessionScope.username}',
+        token: '${sessionScope.csrfToken}',
+        expiration: '${sessionScope.sessionExpiration}'
+    };
+    PAGE.session.startTime = ('${sessionScope.sessionExpiration}'.split(',')[0]) * 1;
+    PAGE.session.startString = new Date(PAGE.session.startTime).toString();
+    PAGE.session.duration = ('${sessionScope.sessionExpiration}'.split(',')[1]) * 1;
+    PAGE.session.endTime = (PAGE.session.startTime + PAGE.session.duration);
+    PAGE.session.endString = new Date(PAGE.session.endTime).toString();
+    PAGE.session.timeout = PAGE.session.endTime;
+    console.log(PAGE);
+</script>
+
+<c:set var="hasVars" value="true"/>
diff --git a/src/main/webapp/WEB-INF/tags/page/wrapper.tag b/src/main/webapp/WEB-INF/tags/page/wrapper.tag
old mode 100644
new mode 100755
index 5cade3f5808b5c3a68005cb5c7b5e97392cc5e2a..6aa402ba31e62578a7af2f5e450a1517036b05d0
--- a/src/main/webapp/WEB-INF/tags/page/wrapper.tag
+++ b/src/main/webapp/WEB-INF/tags/page/wrapper.tag
@@ -6,8 +6,6 @@
 <!--[if IE 9]><html class="ie ie9 ltie10 no-js"><![endif]-->
 <!--[if gt IE 9]><!--><html class="no-js"><!--<![endif]-->
 
-<pg:init/>
-
-<jsp:doBody />
+<jsp:doBody/>
 
 </html>
\ No newline at end of file
diff --git a/src/main/webapp/WEB-INF/tags/page/xnat.tag b/src/main/webapp/WEB-INF/tags/page/xnat.tag
new file mode 100755
index 0000000000000000000000000000000000000000..f3604a8ea79729d553917da1a24babb0e57da25e
--- /dev/null
+++ b/src/main/webapp/WEB-INF/tags/page/xnat.tag
@@ -0,0 +1,662 @@
+<%@ tag description="Document Skeleton" pageEncoding="UTF-8" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="pg" tagdir="/WEB-INF/tags/page" %>
+
+<%@ attribute name="title" %>
+<%@ attribute name="headTop" %>
+<%@ attribute name="headBottom" %>
+<%@ attribute name="bodyTop" %>
+<%@ attribute name="bodyBottom" %>
+
+<head>
+
+    <c:if test="${empty hasInit}">
+        <pg:init>
+            <c:if test="${empty hasVars}">
+                <pg:jsvars/>
+            </c:if>
+        </pg:init>
+    </c:if>
+
+    ${headTop}
+
+    <title>${title}</title>
+
+    <c:set var="_siteRoot" value="${sessionScope.siteRoot}"/>
+    <%--<c:set var="_scripts" value="${_siteRoot}/scripts"/>--%>
+    <%--<c:set var="_scriptsLib" value="${_siteRoot}/scripts/lib"/>--%>
+    <c:set var="_csrfToken" value="${sessionScope.csrfToken}"/>
+    <c:set var="_user" value="${sessionScope.username}"/>
+    <c:set var="versionString" value="v=1.7.0a2"/>
+
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta http-equiv="Pragma" content="no-cache">
+    <meta http-equiv="cache-control" content="max-age=0">
+    <meta http-equiv="cache-control" content="no-cache">
+    <meta http-equiv="expires" content="-1">
+    <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT">
+
+    <!-- load polyfills before ANY other JavaScript -->
+    <script src="${_siteRoot}/scripts/polyfills.js"></script>
+
+    <!-- set global vars that are used often -->
+    <script type="text/javascript">
+
+        var XNAT = {};
+        var serverRoot = '${_siteRoot}';
+        var csrfToken = '${_csrfToken}';
+        //var showReason = typeof false != 'undefined' ? false : null;
+        //var requireReason = typeof false != 'undefined' ? false : null;
+
+    </script>
+
+    <!-- XNAT global functions (no dependencies) -->
+    <script src="${_siteRoot}/scripts/globals.js"></script>
+
+    <!-- required libraries -->
+    <script src="${_siteRoot}/scripts/lib/loadjs/loadjs.js"></script>
+    <script src="${_siteRoot}/scripts/lib/jquery/jquery.min.js"></script>
+    <script src="${_siteRoot}/scripts/lib/jquery/jquery-migrate-1.2.1.min.js"></script>
+    <script type="text/javascript">
+        // use 'jq' to avoid _possible_ conflicts with Velocity
+        var jq = jQuery;
+    </script>
+
+    <!-- jQuery plugins -->
+    <link rel="stylesheet" type="text/css" href="${_siteRoot}/scripts/lib/jquery-plugins/chosen/chosen.min.css?${versionString}">
+    <script src="${_siteRoot}/scripts/lib/jquery-plugins/chosen/chosen.jquery.min.js"></script>
+    <script src="${_siteRoot}/scripts/lib/jquery-plugins/jquery.maskedinput.min.js"></script>
+
+    <%-- probably not going to use the jquery spawner --%>
+    <%--<script src="${_siteRoot}/scripts/lib/jquery-plugins/jquery.spawn.js"></script>--%>
+
+    <!-- other libraries -->
+    <script src="${_siteRoot}/scripts/lib/spawn/spawn.js"></script>
+    <script src="${_siteRoot}/scripts/lib/js.cookie.js"></script>
+
+
+
+
+    <%--<script src="${_siteRoot}/scripts/yui/build/yahoo-dom-event/yahoo-dom-event.js"></script>--%>
+    <%--<script src="${_siteRoot}/scripts/yui/build/event/event-min.js"></script>--%>
+    <%--<script src="${_siteRoot}/scripts/yui/build/container/container-min.js"></script>--%>
+    <%--<script src="${_siteRoot}/scripts/yui/build/menu/menu-min.js"></script>--%>
+    <%--<script src="${_siteRoot}/scripts/yui/build/element/element-beta-min.js"></script>--%>
+    <%--<script src="${_siteRoot}/scripts/yui/build/button/button-min.js"></script>--%>
+    <%--<script src="${_siteRoot}/scripts/yui/build/connection/connection-min.js"></script>--%>
+    <%--<script src="${_siteRoot}/scripts/yui/build/treeview/treeview-min.js"></script>--%>
+    <%--<script src="${_siteRoot}/scripts/yui/build/cookie/cookie-min.js"></script>--%>
+    <%--<script src="${_siteRoot}/scripts/yui/build/tabview/tabview-min.js"></script>--%>
+    <%--<script src="${_siteRoot}/scripts/yui/build/datasource/datasource-min.js"></script>--%>
+    <%--<script src="${_siteRoot}/scripts/yui/build/resize/resize-min.js"></script>--%>
+    <%--<script src="${_siteRoot}/scripts/yui/build/dragdrop/dragdrop-min.js"></script>--%>
+    <%--<script src="${_siteRoot}/scripts/yui/build/datatable/datatable-min.js"></script>--%>
+    <%--<script src="${_siteRoot}/scripts/yui/build/paginator/paginator-min.js"></script>--%>
+    <%--<script src="${_siteRoot}/scripts/yui/build/json/json-min.js"></script>--%>
+    <%--<script src="${_siteRoot}/scripts/yui/xnat_loader.js"></script>--%>
+    <%--<script src="${_siteRoot}/scripts/LeftBarTreeView.js"></script>--%>
+    <%--<script src="${_siteRoot}/scripts/justification/justification.js"></script>--%>
+
+
+
+
+    <!-- XNAT utility functions -->
+    <script src="${_siteRoot}/scripts/utils.js"></script>
+
+    <script type="text/javascript">
+
+        /*
+         * XNAT global namespace object, which will not be overwriten if
+         * already defined. Also define some other top level namespaces.
+         */
+        extend(XNAT, {
+            /*
+             * Parent namespace that templates can use to put their
+             * own namespace
+             */
+            app: {
+                displayNames: {
+                    singular: {
+                        project: "Project",
+                        subject: "Subject",
+                        imageSession: "Session",
+                        mrSession: "MR Session"
+                    },
+                    plural: {
+                        project: "Projects",
+                        subject: "Subjects",
+                        imageSession: "Sessions",
+                        mrSession: "MR Sessions"
+                    }
+                },
+                siteId: "XNAT"
+            }
+        });
+
+    </script>
+    <script type="text/javascript">
+        // initialize "Chosen" menus on DOM load
+        // all <select class="chosen-menu"> elements
+        // will be converted
+        // putting this here to be at the top of
+        // the jQuery DOM-ready queue
+        jq(function(){
+            chosenInit()
+        });
+    </script>
+
+    <script type="text/javascript">
+
+        XNAT.dom = getObject(XNAT.dom || {});
+        XNAT.dom.addFormCSRF = function($form){
+            $form = $$($form || 'form');
+            $form.each(function(form){
+                var form$ = $(form);
+                // don't add the hidden input twice
+                if (!form$.has('input[name="XNAT_CSRF"]')) {
+                    form$.append('<input type="hidden" name="XNAT_CSRF" value="' + csrfToken + '">');
+                }
+            });
+        };
+
+        jq(function(){
+            // add hidden input with CSRF data
+            // to all forms on page load
+            XNAT.dom.addFormCSRF();
+        });
+
+    </script>
+
+    <!-- YUI css -->
+    <%--<link rel="stylesheet" type="text/css" href="${_siteRoot}/scripts/yui/build/assets/skins/sam/skin.css?v=1.7.0a1">--%>
+
+    <!-- xdat.css and xnat.css loaded last to override YUI styles -->
+    <link rel="stylesheet" type="text/css" href="${_siteRoot}/style/app.css?${versionString}">
+
+
+    <!-- legacy XNAT scripts -->
+    <link rel="stylesheet" type="text/css" href="${_siteRoot}/scripts/xmodal/xmodal.css?${versionString}">
+    <script src="${_siteRoot}/scripts/xmodal/xmodal.js"></script>
+    <script src="${_siteRoot}/scripts/xmodal/xmodal-migrate.js"></script>
+
+    <%--<link rel="stylesheet" type="text/css" href="${_siteRoot}/scripts/tabWrangler/tabWrangler.css?${versionString}1">--%>
+    <%--<script src="${_siteRoot}/scripts/tabWrangler/tabWrangler.js"></script>--%>
+
+    <!-- date input stuff -->
+    <%--<link type="text/css" rel="stylesheet" href="${_siteRoot}/scripts/yui/build/calendar/assets/skins/sam/calendar.css?${versionString}">--%>
+    <%--<script src="${_siteRoot}/scripts/yui/build/calendar/calendar-min.js"></script>--%>
+    <%--<script src="${_siteRoot}/scripts/ezCalendar.js"></script>--%>
+
+    <!-- XNAT JLAPI scripts -->
+    <script src="${_siteRoot}/scripts/xnat/url.js"></script>
+    <script src="${_siteRoot}/scripts/xnat/xhr.js"></script>
+    <script src="${_siteRoot}/scripts/xnat/event.js"></script>
+    <script src="${_siteRoot}/scripts/xnat/element.js"></script>
+    <script src="${_siteRoot}/scripts/xnat/ui/table.js"></script>
+    <script src="${_siteRoot}/scripts/xnat/ui/panel.js"></script>
+    <script src="${_siteRoot}/scripts/xnat/ui/tabs.js"></script>
+    <script src="${_siteRoot}/scripts/xnat/ui/popup.js"></script>
+    <script src="${_siteRoot}/scripts/xnat/ui/dialog.js"></script>
+
+    <script src="${_siteRoot}/scripts/timeLeft.js"></script>
+
+${headBottom}
+
+</head>
+<body id="xnat-app" class="xnat app">
+
+${bodyTop}
+
+<div id="page_wrapper">
+
+    <div id="user_bar">
+        <div class="inner">
+            <img id="attention_icon" src="${_siteRoot}/images/attention.png" style="display:none;" alt="attention needed - click for more info" title="attention needed - click for more info">
+            <span id="user_info">Logged in as: &nbsp;<a href="${_siteRoot}/app/template/XDATScreen_UpdateUser.vm">${_user}</a> <b>|</b>
+                <span class="tip_icon" style="margin-right:3px;left:2px;top:3px;">
+                    <span class="tip shadowed" style="top:20px;z-index:10000;white-space:normal;left:-150px;width:300px;background-color:#ffc;">
+                        Your XNAT session will auto-logout after a certain period of inactivity. 
+                        You can reset the timer without reloading thepage by clicking "renew."
+                    </span>
+                </span>
+                Auto-logout in: 
+                <b id="timeLeft">0:13:53</b> - 
+                <a id="timeLeftRenew" href="#!">renew</a>
+                <b>|</b> 
+                <a id="logout_user" href="${_siteRoot}/app/action/LogoutUser">Logout</a>
+            </span>
+            <script type="text/javascript">
+                $('#timeLeftRenew').click(XNAT.app.timeout.handleOk);
+                Cookies.set('guest', 'false', {path: '/'});
+            </script>
+            <div class="clear"></div>
+        </div>
+    </div><!-- /user_bar -->
+
+
+    <div id="main_nav">
+
+        <ul class="nav">
+            <!-- Sequence: 10 -->
+            <!-- allowGuest: true -->
+            <li>
+                <a id="nav-home" title="Home" href="${_siteRoot}/">&nbsp;</a>
+                <script>
+                    $('#nav-home').css({
+                        width: '30px',
+                        backgroundImage: "url('/images/xnat-nav-logo-white-lg.png')",
+                        backgroundRepeat: 'no-repeat',
+                        backgroundSize: '32px',
+                        backgroundPosition: 'center'
+                    });
+                </script>
+            </li>
+            <!-- Sequence: 20 -->
+            <li class="more"><a href="#new">New</a>
+                <ul class="" style="display: none;">
+                    <!-- Sequence: 10 -->
+                    <li><a href="${_siteRoot}/app/template/XDATScreen_add_xnat_projectData.vm">Project</a></li>
+                    <li><a href="${_siteRoot}/app/action/XDATActionRouter/xdataction/edit/search_element/xnat%3AsubjectData">Subject</a></li>
+                    <li><a href="${_siteRoot}/app/template/XDATScreen_add_experiment.vm">Experiment</a></li>
+                </ul>
+            </li>
+            <!-- Sequence: 30 -->
+            <li class="more"><a href="#upload">Upload</a>
+                <ul>
+                    <!-- Sequence: 10 -->
+                    <!-- Upload/Default -->
+                    <li><a href="${_siteRoot}/app/template/LaunchUploadApplet.vm">Images</a></li>
+                    <li><a href="${_siteRoot}/app/template/XMLUpload.vm">XML</a></li>
+                    <li><a href="${_siteRoot}/app/template/XDATScreen_uploadCSV.vm">Spreadsheet</a></li>
+                    <li><a href="${_siteRoot}/app/template/XDATScreen_prearchives.vm">Go to prearchive</a></li>
+                </ul>
+            </li>
+
+            <c:if test="${isAdmin == true}">
+            <!-- Sequence: 40 -->
+            <li class="more"><a href="#adminbox">Administer</a>
+                <ul>
+                    <!-- Sequence: 10 -->
+                    <li><a href="${_siteRoot}/app/template/XDATScreen_admin.vm">Users</a></li>
+                    <li><a href="${_siteRoot}/app/template/XDATScreen_groups.vm">Groups</a></li>
+                    <li><a href="${_siteRoot}/app/template/XDATScreen_dataTypes.vm">Data Types</a></li>
+                    <li><a href="${_siteRoot}/app/template/XDATScreen_email.vm">Email</a></li>
+                    <li><a href="${_siteRoot}/app/template/XDATScreen_manage_pipeline.vm">Pipelines</a></li>
+                    <li><a href="${_siteRoot}/app/template/Configuration.vm">Configuration</a></li>
+                    <li><a href="${_siteRoot}/app/template/Scripts.vm">Automation</a></li>
+                    <li><a href="${_siteRoot}/app/template/XDATScreen_admin_options.vm">More...</a></li>
+                </ul>
+            </li>
+            </c:if>
+
+
+            <!-- Title: Tools -->
+            <!-- Sequence: 50 -->
+            <!-- allowGuest: true -->
+
+            <li class="more"><a href="#tools">Tools</a>
+                <ul>
+                    <!-- Sequence: 10 -->
+                    <!-- allowGuest: true -->
+                    <li><a href="https://wiki.xnat.org/display/XNAT16/XNAT+Desktop" target="_blank">XNAT Desktop (XND)</a></li>
+                    <li><a href="http://nrg.wustl.edu/projects/DICOM/DicomBrowser.jsp" target="_blank">DICOM Browser</a></li>
+                    <li><a href="https://wiki.xnat.org/display/XNAT16/XNAT+Client+Tools" target="_blank">Command Prompt Tools</a></li>
+                </ul>
+            </li>
+            <!-- Sequence: 60 -->
+            <li class="more"><a href="#help">Help</a>
+                <ul class="" style="display: none;">
+                    <!-- Sequence: 10 -->
+                    <!-- Home/Default -->
+                    <li><a href="${_siteRoot}/app/template/ReportIssue.vm">Report a Problem</a></li>
+                    <li><a href="http://wiki.xnat.org/display/XNAT16/Home" target="_blank">Documentation</a></li>
+                </ul>
+            </li>
+        </ul>
+        
+        <!-- search script -->
+        <script type="text/javascript">
+            <!--
+            function DefaultEnterKey(e, button){
+                var keynum, keychar, numcheck;
+
+                if (window.event) // IE
+                {
+                    keynum = e.keyCode;
+                    if (keynum == 13) {
+                        submitQuickSearch();
+                        return true;
+                    }
+                }
+                else if (e) // Netscape/Firefox/Opera
+                {
+                    keynum = e.which;
+                    if (keynum == 13) {
+                        submitQuickSearch();
+                        return false;
+                    }
+                }
+                return true;
+            }
+
+            function submitQuickSearch(){
+                concealContent();
+                if (document.getElementById('quickSearchForm').value != "")
+                    document.getElementById('quickSearchForm').submit();
+            }
+
+            //-->
+        </script>
+        <!-- end search script -->
+
+        <style type="text/css">
+            #quickSearchForm .chosen-results {
+                max-height: 500px;
+            }
+
+            #quickSearchForm .chosen-results li {
+                padding-right: 20px;
+                white-space: nowrap;
+            }
+
+            #quickSearchForm .chosen-container .chosen-drop {
+                width: auto;
+                min-width: 180px;
+                max-width: 360px;
+            }
+
+            #quickSearchForm .chosen-container .chosen-drop .divider {
+                padding: 0;
+                overflow: hidden;
+            }
+        </style>
+
+        <form id="quickSearchForm" method="post" action="${_siteRoot}/app/action/QuickSearchAction">
+            <select id="stored-searches" data-placeholder="Stored Searches" style="display: none;">
+                <option></option>
+                <optgroup>
+                    <option value="${_siteRoot}/app/template/XDATScreen_search_wizard1.vm">Advanced Search…</option>
+                </optgroup>
+                <optgroup class="stored-search-list">
+                    <option disabled="">(no stored searches)</option>
+                    <!-- stored searches will show up here -->
+                </optgroup>
+            </select>
+            <input id="searchValue" class="clean" name="searchValue" type="text" maxlength="40" size="20" value="">
+            <button type="button" id="search_btn" class="btn2" onclick="submitQuickSearch();">Go</button>
+
+            <script>
+                $('#searchValue').each(function(){
+                    var _this = this;
+                    _this.value = _this.value || 'search';
+                    $(_this).focus(function(){
+                        $(_this).removeClass('clean');
+                        if (!_this.value || _this.value === 'search') {
+                            _this.value = '';
+                        }
+                    })
+                });
+                
+                $('#stored-searches').on('change', function(){
+                    if (this.value) {
+                        window.location.href = this.value;
+                    }
+                }).chosen({
+                    width: '150px',
+                    disable_search_threshold: 9,
+                    inherit_select_classes: true,
+                    placeholder_text_single: 'Stored Searches',
+                    search_contains: true
+                });
+            </script>
+        </form>
+
+    </div>
+    <!-- /main_nav -->
+
+    <!-- main_nav interactions -->
+    <script type="text/javascript">
+
+        (function(){
+
+            // cache it
+            var main_nav$ = jq('#main_nav > ul');
+            
+            var body$ = jq('body');
+
+            var cover_up_count = 1;
+
+            function coverApplet(el$){
+                var cover_up_id = 'cover_up' + cover_up_count++;
+                var jqObjPos = el$.offset(),
+                    jqObjLeft = jqObjPos.left,
+                    jqObjTop = jqObjPos.top,
+                    jqObjMarginTop = el$.css('margin-top'),
+                    jqObjWidth = el$.outerWidth() + 4,
+                    jqObjHeight = el$.outerHeight() + 2;
+
+                el$.before('<iframe id="' + cover_up_id + '" class="applet_cover_up" src="about:blank" width="' + jqObjWidth + '" height="' + jqObjHeight + '"></iframe>');
+
+                jq('#' + cover_up_id).css({
+                    display: 'block',
+                    position: 'fixed',
+                    width: jqObjWidth,
+                    height: jqObjHeight,
+                    marginTop: jqObjMarginTop,
+                    left: jqObjLeft,
+                    top: jqObjTop,
+                    background: 'transparent',
+                    border: 'none',
+                    outline: 'none'
+                });
+            }
+
+            function unCoverApplets(el$){
+                el$.prev('iframe.applet_cover_up').detach();
+            }
+
+            function fadeInNav(el$){
+//            el$.stop('clearQueue','gotoEnd');
+                el$.find('> ul').show().addClass('open');
+            }
+
+            function fadeOutNav(el$){
+//            el$.stop('clearQueue','gotoEnd');
+                el$.find('> ul').hide().removeClass('open');
+            }
+
+            // give menus with submenus a class of 'more'
+            main_nav$.find('li ul, li li ul').closest('li').addClass('more');
+            main_nav$.find('li li ul').addClass('subnav');
+
+            // no fancy fades on hover
+            main_nav$.find('li.more').on('mouseover',
+                    function(){
+                        var li$ = $(this);
+                        fadeInNav(li$);
+                        //jq('#main_nav li').removeClass('open');
+                        li$.find('ul.subnav').each(function(){
+                            var sub$ = $(this);
+                            var offsetL = sub$.closest('ul').outerWidth();
+                            sub$.css({'left': offsetL + -25})
+                        });
+                        if (body$.hasClass('applet')) {
+                            coverApplet(li$.find('> ul'));
+                        }
+                    }
+            ).on('mouseout',
+                    function(){
+                        var li$ = $(this);
+                        fadeOutNav(li$);
+                        if (body$.hasClass('applet')) {
+                            unCoverApplets(li$.find('> ul'));
+                        }
+                    }
+            );
+
+            // clicking the "Logout" link sets the warning bar cookie to 'OPEN' so it's available if needed on next login
+            jq('#logout_user').click(function(){
+                Cookies.set('WARNING_BAR', 'OPEN', {path: '/'});
+                Cookies.set('NOTIFICATION_MESSAGE', 'OPEN', {path: '/'});
+            });
+
+        })();
+    </script>
+    <!-- end main_nav interactions -->
+
+    <div id="header" class="main_header">
+        <div class="pad">
+            <a id="header_logo" href="${_siteRoot}/" style="display: none;" title="XNAT version Unknown">
+                <img class="logo_img" src="${_siteRoot}/images/logo.png" style="border:none;">
+            </a>
+        </div>
+    </div>
+    <!-- /header -->
+
+    <script type="text/javascript">
+
+        (function(){
+
+            var header_logo$ = $('#header_logo');
+
+            // adjust height of header if logo is taller than 65px
+            var hdr_logo_height = header_logo$.height();
+            if (hdr_logo_height > 65) {
+                jq('.main_header').height(hdr_logo_height + 10);
+            }
+
+            // adjust width of main nav if logo is wider than 175px
+            var hdr_logo_width = header_logo$.width();
+            if (hdr_logo_width > 175) {
+                jq('#main_nav').width(932 - hdr_logo_width - 20);
+            }
+
+            //
+            //var recent_proj_height = jq('#min_projects_list > div').height();
+            var recent_proj_height = 67;
+            //jq('#min_projects_list, #min_expt_list').height(recent_proj_height * 5).css({'min-width':349,'overflow-y':'scroll'});
+
+        })();
+
+        logged_in = true;
+
+        // initialize the advanced search method toggler
+        XNAT.app.searchMethodToggler = function(parent$){
+
+            parent$ = $$(parent$ || 'body');
+
+            var INPUTS = 'input, select, textarea, :input',
+                    SEARCH_METHOD_CKBOXES = 'input.search-method',
+                    searchGroups$ = parent$.find('div.search-group'),
+                    searchMethodInputs$ = parent$.find(SEARCH_METHOD_CKBOXES);
+
+            // disable 'by-id' search groups by default
+            searchGroups$.filter('.by-id').addClass('disabled').find(INPUTS).not(SEARCH_METHOD_CKBOXES).changeVal('').prop('disabled', true).addClass('disabled');
+
+            // enable 'by-criteria' search groups by default
+            searchGroups$.filter('.by-criteria').removeClass('disabled').find(INPUTS).prop('disabled', false).removeClass('disabled');
+
+            // check 'by-criteria' checkboxes
+            searchMethodInputs$.filter('.by-criteria').prop('checked', true);
+
+            // don't add multiple click handlers
+            searchMethodInputs$.off('click');
+
+            // toggle the search groups
+            searchMethodInputs$.on('click', function(){
+
+                var method = this.value,
+                    isChecked = this.checked;
+
+                searchGroups$.addClass('disabled').find(INPUTS).not(SEARCH_METHOD_CKBOXES).changeVal('').prop('disabled', true).addClass('disabled');
+
+                searchGroups$.filter('.' + method).removeClass('disabled').find(INPUTS).prop('disabled', false).removeClass('disabled');
+
+                // update the radio buttons/checkboxes
+                searchMethodInputs$.prop('checked', false);
+                searchMethodInputs$.filter('.' + method).prop('checked', true);
+                chosenUpdate();
+            });
+        };
+
+    </script>
+
+    <div id="tp_fm"></div>
+
+    <div id="breadcrumbs"></div>
+    <script src="${_siteRoot}/scripts/xnat/ui/breadcrumbs.js"></script>
+    <script language="javascript">
+        window.isProjectPage = (XNAT.data.context.xsiType === 'xnat:projectData');
+        // wrap it up to keep things
+        // out of global scope
+        (function(){
+            var crumbs = [];
+            XNAT.ui.breadcrumbs.render('#breadcrumbs', crumbs);
+        })();
+    </script>
+
+    <div id="layout_content2" style="display:none;">Loading...</div>
+    <div id="layout_content">
+        <!--BEGIN SCREEN CONTENT -->
+        <!-- start xnat-templates/screens/Page.vm -->
+        <script src="${_siteRoot}/scripts/xnat/app/customPage.js"></script>
+
+        <div id="view-page">
+            <!--   BODY START   -->
+
+
+            <jsp:doBody/>
+
+
+            <!--  BODY END  -->
+        </div>
+
+        <!-- end xnat-templates/screens/Page.vm -->
+        <!--END SCREEN CONTENT -->
+    </div>
+
+    <div id="mylogger"></div>
+</div>
+<!-- /page_wrapper -->
+
+<div id="xnat_power">
+    <a target="_blank" href="http://www.xnat.org/" style="" title="XNAT version Unknown"><img
+            src="${_siteRoot}/images/xnat_power_small.png"></a>
+    <small>version Unknown</small>
+</div>
+
+<script type="text/javascript">
+
+    loadjs(scriptUrl('xnat/event.js'), function(){
+
+        // shift-click the header or footer XNAT logo to ENABLE debug mode
+        // alt-shift-click to DISABLE debug mode
+        // ctrl-alt-click to open the Swagger page in a new window
+        XNAT.event.click('#header_logo, #xnat_power > a')
+            .shiftKey(function(e){
+                e.preventDefault();
+                window.location.hash = 'debug=on';
+                window.location.reload();
+            })
+            .altShift(function(e){
+                e.preventDefault();
+                window.location.hash = 'debug=off';
+                window.location.reload();
+            })
+            .ctrlAlt(function(e){
+                e.preventDefault();
+                XNAT.ui.popup('/xapi/swagger-ui.html');
+            });
+
+    })
+
+</script>
+<script src="${_siteRoot}/scripts/footer.js"></script>
+
+<div id="xmodal-loading" style="position:fixed;left:-9999px;top:-9999px;">
+    <img src="${_siteRoot}/scripts/xmodal/loading_bar.gif" alt="loading">
+</div>
+
+${bodyBottom}
+
+</body>
\ No newline at end of file
diff --git a/src/main/webapp/page/admin/content.jsp b/src/main/webapp/page/admin/content.jsp
old mode 100644
new mode 100755
index 7965950fe9aeff2f593dc2b4ebf07a7d1c1f7dac..7532863935c23a740eecbd6deb53bf8ec30cc517
--- a/src/main/webapp/page/admin/content.jsp
+++ b/src/main/webapp/page/admin/content.jsp
@@ -6,6 +6,10 @@
     <pg:init/>
 </c:if>
 
+<c:if test="${empty hasVars}">
+    <pg:jsvars/>
+</c:if>
+
 <script src="${themeRoot}/scripts/xnat/ui/table.js"></script>
 <script src="${themeRoot}/scripts/xnat/ui/panel.js"></script>
 <script src="${themeRoot}/scripts/xnat/ui/tabs.js"></script>
diff --git a/src/main/webapp/page/admin/info/index.jsp b/src/main/webapp/page/admin/info/index.jsp
old mode 100644
new mode 100755
index 545f0509567825e1c9a2b93fcedce08f8d5349c6..11b12c9e9bcecc625bf311f8bd9471fb0cb65c40
--- a/src/main/webapp/page/admin/info/index.jsp
+++ b/src/main/webapp/page/admin/info/index.jsp
@@ -2,21 +2,11 @@
 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
 <%@ taglib prefix="pg" tagdir="/WEB-INF/tags/page" %>
 
-<c:set var="pageName" value="info"/>
+<c:set var="pageName" value="info" scope="request"/>
 
 <pg:wrapper>
 
-    <pg:head>
-        <jsp:attribute name="headTop">
-            <!-- headTop -->
-        </jsp:attribute>
-
-        <!-- custom content for head element -->
-
-        <jsp:attribute name="headBottom">
-            <!-- headBottom -->
-        </jsp:attribute>
-    </pg:head>
+    <pg:head/>
 
     <pg:content id="${pageName}" className="xnat app ${pageName}">
         <div id="page-wrapper">
diff --git a/src/main/webapp/page/content.jsp b/src/main/webapp/page/content.jsp
old mode 100644
new mode 100755
index 62b4c0f59caab3eb5df5d92f2ca85c62b23fa27a..cb040c84c3fb01c106ae7bab4463a9f6e8e1569c
--- a/src/main/webapp/page/content.jsp
+++ b/src/main/webapp/page/content.jsp
@@ -6,6 +6,14 @@
     <pg:init/>
 </c:if>
 
+<c:if test="${not empty hasInit}">
+    <!-- init done -->
+</c:if>
+
+<c:if test="${empty hasVars}">
+    <pg:jsvars/>
+</c:if>
+
 <div id="page-wrapper">
     <div class="pad">
         <div id="page-content">loading...</div>
diff --git a/src/main/webapp/page/index.jsp b/src/main/webapp/page/index.jsp
old mode 100644
new mode 100755
index 151538a3fdb1aab098f6fe4b550e5eeefdc745d4..4899f258848ab759fc1857b77b47246970e15232
--- a/src/main/webapp/page/index.jsp
+++ b/src/main/webapp/page/index.jsp
@@ -9,7 +9,12 @@
     <pg:head/>
 
     <pg:content id="${pageName}" className="xnat app ${pageName}">
-        <jsp:include page="content.jsp"/>
+        <div id="page-wrapper">
+            <div class="pad">
+                <jsp:include page="content.jsp"/>
+            </div>
+        </div>
+        <!-- /#page-wrapper -->
     </pg:content>
 
 </pg:wrapper>
diff --git a/src/main/webapp/page/test/content.jsp b/src/main/webapp/page/test/content.jsp
new file mode 100755
index 0000000000000000000000000000000000000000..ab9243afab935786141b922d3b456590d0be8540
--- /dev/null
+++ b/src/main/webapp/page/test/content.jsp
@@ -0,0 +1,14 @@
+<%@ page session="true" contentType="text/html" pageEncoding="UTF-8" language="java" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="pg" tagdir="/WEB-INF/tags/page" %>
+
+<div id="page-wrapper">
+    <div class="pad">
+
+        <h1>${pageName}</h1>
+
+        <h2>PUT YOUR STUFF HERE</h2>
+
+    </div>
+</div>
+<!-- /#page-wrapper -->
diff --git a/src/main/webapp/page/test/index.jsp b/src/main/webapp/page/test/index.jsp
new file mode 100755
index 0000000000000000000000000000000000000000..9273e39deade0395cea528135370906d099c6171
--- /dev/null
+++ b/src/main/webapp/page/test/index.jsp
@@ -0,0 +1,13 @@
+<%@ page session="true" contentType="text/html" pageEncoding="UTF-8" language="java" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="pg" tagdir="/WEB-INF/tags/page" %>
+
+<c:set var="pageName" value="test" scope="request"/>
+
+<pg:wrapper>
+    <pg:xnat>
+
+        <jsp:include page="content.jsp"/>
+
+    </pg:xnat>
+</pg:wrapper>
diff --git a/src/main/webapp/pages/foo.jsp b/src/main/webapp/pages/foo.jsp
new file mode 100755
index 0000000000000000000000000000000000000000..b88053549c9c05f3fc76783e524519bb46ba8a75
--- /dev/null
+++ b/src/main/webapp/pages/foo.jsp
@@ -0,0 +1,3 @@
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+
+<h1>Foo</h1>
diff --git a/src/main/webapp/scripts/inbox.js b/src/main/webapp/scripts/inbox.js
deleted file mode 100644
index 2e6cecaf85bf5f7da01ce0eb1b148449eb1d5a1f..0000000000000000000000000000000000000000
--- a/src/main/webapp/scripts/inbox.js
+++ /dev/null
@@ -1,391 +0,0 @@
-/**
- * Script is likely not used. Code commented to prepare for deletion.
- */
-
-//function Inbox(div, servlet_url, project) {
-//  if (0 == arguments.length) { return; }   // bail early on prototype creation
-//
-//  this.div = div;
-//  var projectspec = '&project=' + project;
-//
-//  var baseURL = servlet_url + '?remote-class=org.nrg.xnat.ajax.Inbox';
-//  this.listURL = baseURL + '&remote-method=list' + projectspec;
-//  this.startImportURL = baseURL + '&remote-method=startImport' + projectspec;
-//  this.monitorImportURL = baseURL + '&remote-method=monitorImport' + projectspec;
-//  this.removeURL = baseURL + '&remote-method=remove' + projectspec;
-//
-//  this.refreshActions = [];
-//
-//  this.loading = false;
-//  this.importing = null;
-//  this.importInterval = null;
-//
-//  this.listreq = null;
-//  this.importreq = null;
-//  this.monitorreq = null;
-//
-//  // Callbacks don't get called in object context, so if we need access
-//  // to this object we have to set the callbacks up as closures.
-//  var instance = this;
-//
-//  this.listCallback = function() {
-//    if (4 == instance.listreq.readyState) {	// state: loaded
-//      if (200 == instance.listreq.status) {	// status: OK
-//	// Clear the inbox area before updating.
-//	instance.loading = false;
-//	while (null != instance.div.firstChild) {
-//	  instance.div.removeChild(instance.div.firstChild);
-//	}
-//
-//	var response = instance.listreq.responseXML;
-//	if (null == response) {
-//	  // We've probably lost the session.  Reload the page.
-//	  window.clearInterval(this.importInterval);
-//	  this.importInterval = null;
-//      xModalMessage('FTP Inbox', "Unable to get listing; session may have timed out.");
-//	  location.reload();
-//	  return;
-//	}
-//
-//	var files = response.getElementsByTagName("file");
-//	if (files.length > 0) {
-//	  var listTable = document.createElement('table');
-//	  var caption = document.createElement('caption');
-//	  caption.appendChild(document.createTextNode('FTP inbox'));
-//	  listTable.appendChild(caption);
-//
-//	  var thead = document.createElement('thead');
-//	  listTable.appendChild(thead);
-//
-//	  var tr = document.createElement('tr');
-//	  thead.appendChild(tr);
-//	  var th = document.createElement('th');
-//	  tr.appendChild(th);
-//	  th.appendChild(document.createTextNode('Received files'));
-//
-//	  th = document.createElement('th');
-//	  tr.appendChild(th);
-//	  th.appendChild(document.createTextNode('Size'));
-//
-//	  var tbody = document.createElement('tbody');
-//	  listTable.appendChild(tbody);
-//
-//	  for (var i = 0; i < files.length; i++) {
-//	    var f = files[i];
-//
-//	    tr = document.createElement('tr');
-//	    var td = document.createElement('td');
-//	    var selectBox = document.createElement('input');
-//	    selectBox.setAttribute('type', 'checkbox');
-//	    selectBox.setAttribute('value', f.firstChild.data);
-//	    td.appendChild(selectBox);
-//	    td.appendChild(document.createTextNode(f.firstChild.data));
-//	    tr.appendChild(td);
-//
-//	    td = document.createElement('td');
-//	    td.appendChild(document.createTextNode(f.getAttribute('size') + ' kb'));
-//	    tr.appendChild(td);
-//
-//	    // TODO: marker for folder
-//	    // TODO: allow descent into folders
-//	    // TODO: allow folder selection?
-//
-//	    tbody.appendChild(tr);
-//	  }
-//
-//	  // table and actions each get put in their own div for layout
-//	  var tableDiv = document.createElement('div');
-//	  tableDiv.className = 'content';
-//	  tableDiv.appendChild(listTable);
-//	  instance.div.appendChild(tableDiv);
-//
-//	  var actionDiv = document.createElement('div');
-//	  actionDiv.id = 'inboxActions';
-//	  actionDiv.className = 'actions';
-//	  instance.div.appendChild(actionDiv);
-//
-//	  if ('true' == response.documentElement.getAttribute('locked')) {
-//        xModalMessage('FTP Inbox', 'FTP Inbox import in progress');
-//	    actionDiv.appendChild(document.createTextNode(
-//		    'Inbox contents are being imported into prearchive'));
-//	  } else {
-//	    var deleteButton = document.createElement('input');
-//	    deleteButton.setAttribute('type', 'button');
-//	    deleteButton.setAttribute('value', 'Delete selected files');
-//	    deleteButton.onclick = function() { instance.doDelete(); };
-//	    actionDiv.appendChild(deleteButton);
-//
-//	    var importButton = document.createElement('input');
-//	    importButton.setAttribute('type', 'button');
-//	    importButton.setAttribute('value', 'Import selected files');
-//	    importButton.onclick = function() { instance.doImport(); };
-//	    actionDiv.appendChild(importButton);
-//	  }
-//
-//	  instance.div.appendChild(document.createElement('br'));
-//	  instance.div.appendChild(document.createElement('hr'));
-//	}
-//      } else {
-//	instance.loading = false;
-//	while (null != instance.div.firstChild) {
-//	  instance.div.removeChild(instance.div.firstChild);
-//	}
-//        xModalMessage('FTP Inbox', "Unable to load FTP inbox (error " + instance.listreq.status + "): " + instance.listreq.responseText);
-//      }
-//    }
-//  }
-//
-//  this.importCallback = function() {
-//    instance.checkImport(instance.importreq);
-//  }
-//
-//  this.monitorCallback = function() {
-//    instance.checkImport(instance.monitorreq);
-//  }
-//
-//  this.monitorImport = function() {
-//    if (window.XMLHttpRequest) {
-//      instance.monitorreq = new XMLHttpRequest();
-//    } else if (window.ActiveXObject) {
-//      instance.monitorreq = new ActiveXObject("Microsoft.XMLHTTP");
-//    }
-//    instance.monitorreq.open("GET", instance.monitorImportURL, true);
-//    instance.monitorreq.onreadystatechange = this.monitorCallback;
-//    instance.monitorreq.send(null);
-//  }
-//}
-//
-//// Create and discard a dummy object to force prototype object creation
-//new Inbox();
-//
-//Inbox.prototype.getSelection = function() {
-//  // Get the list of selected items
-//  var selection = '';
-//  var inputs = this.div.getElementsByTagName('input');
-//  for (var i = 0; i < inputs.length; i++) {
-//    var input = inputs[i];
-//    if ('checkbox' == input.getAttribute('type')
-//	&& true == input.checked) {
-//      selection += '&path=' + input.getAttribute('value');
-//    }
-//  }
-//  return selection;
-//}
-//
-//
-//Inbox.prototype.doDelete = function() {
-//  var selection = this.getSelection();
-//  if ('' == selection) {
-//    xModalMessage('FTP Inbox', 'No files have been selected, so none will be deleted.');
-//  } else if (confirm('Really delete selected files from inbox?')) {
-//    // Clear the inbox space
-//    while (null != this.div.firstChild) {
-//      this.div.removeChild(this.div.firstChild);
-//    }
-//
-//    // The response from the delete is the same as for a list request
-//    // TODO: test for browser support?
-//    if (window.XMLHttpRequest) {
-//      this.listreq = new XMLHttpRequest();
-//    } else if (window.ActiveXObject) {
-//      this.listreq = new ActiveXObject("Microsoft.XMLHTTP");
-//    }
-//    this.listreq.open("GET", this.removeURL + selection, true);
-//    this.listreq.onreadystatechange = this.listCallback;
-//    this.listreq.send(null);
-//  }
-//}
-//
-//Inbox.prototype.doImport = function() {
-//  var selection = this.getSelection();
-//  if ('' != selection
-//      || confirm('No files selected; import all files from inbox?')) {
-//    // Clear the inbox space
-//    while (null != this.div.firstChild) {
-//      this.div.removeChild(this.div.firstChild);
-//    }
-//    var messageDiv = document.createElement('div');
-//    messageDiv.className = 'importlog';
-//    this.div.appendChild(messageDiv);
-//    var messagep = document.createElement('p');
-//    messagep.className = 'header';
-//    messagep.appendChild(document.createTextNode('Importing files from FTP inbox'));
-//    messageDiv.appendChild(messagep);
-//    messageDiv.scrollTop = messageDiv.scrollHeight;
-//
-//    // TODO: test for browser support?
-//    if (window.XMLHttpRequest) {
-//      this.importreq = new XMLHttpRequest();
-//    } else if (window.ActiveXObject) {
-//      this.importreq = new ActiveXObject("Microsoft.XMLHTTP");
-//    }
-//    this.importreq.open("GET", this.startImportURL + selection, true);
-//    this.importreq.onreadystatechange = this.importCallback;
-//    this.importreq.send(null);
-//
-//    var instance = this;
-//    this.importInterval = window.setInterval(function() {
-//					       instance.monitorImport();
-//					     }, 2000);
-//  }
-//}
-//
-//
-//Inbox.prototype.checkImport = function(req) {
-//  if (4 == req.readyState) {
-//    if (200 == req.status) {
-//      var response = req.responseXML;
-//      if (null == response) {
-//	// We've probably lost the session.  Reload the page.
-//	window.clearInterval(this.importInterval);
-//	this.importInterval = null;
-//    xModalMessage('FTP Inbox', "Unable to import; session may have timed out.");
-//	location.reload();
-//	return;
-//      }
-//
-//      var statusa = response.getElementsByTagName("status");
-//      if (1 == statusa.length) {
-//	var status = statusa[0];
-//
-//	// idle indicator
-//	if (0 == status.childNodes.length) {
-//	  var lastMessage = this.div.firstChild.lastChild;
-//	  if (null != lastMessage) {
-//	    lastMessage.appendChild(document.createTextNode('.'));
-//	  }
-//	}
-//
-//	var object = null;
-//	for (var i = 0; i < status.childNodes.length; i++) {
-//	  var statusline = status.childNodes[i];
-//	  statusline.normalize();
-//	  var tagName = statusline.tagName;
-//	  object = statusline.getAttribute("object");
-//
-//	  var text = "";
-//	  for (var j = 0; j < statusline.childNodes.length; j++) {
-//	    // 3 = Node.TEXT_NODE
-//	    if (3 == statusline.childNodes[j].nodeType) {
-//	      text += statusline.childNodes[j].nodeValue;
-//	    }
-//	  }
-//
-//	  var message = document.createElement('p');
-//	  message.className = tagName;
-//	  message.appendChild(document.createTextNode(text));
-//	  var messageDiv = this.div.firstChild;
-//	  messageDiv.appendChild(message);
-//	  messageDiv.scrollTop = messageDiv.scrollHeight;
-//
-//	  // The very first status message is a 'processing' on the
-//	  // inbox object.  Remember this object, because that's our
-//	  // cue for knowing when the import is done.
-//	  if (null == this.importing) {
-//	    if ('processing' == tagName) {
-//	      this.importing = object;
-//	    } else {
-//          xModalMessage('FTP Inbox', "Received unexpected status message " + tagName);
-//	    }
-//	  }
-//	}
-//
-//	// If the last message refers to the object being processed,
-//	// check for success or failure message.
-//	if (null != this.importing && this.importing == object) {
-//	  switch (tagName) {
-//	  case 'processing':
-//	  case 'warning':
-//	    break;
-//
-//	  case 'completed':
-//	    this.finishImportWithMessage('Import complete');
-//	    break;
-//
-//	  case 'failure':
-//	    this.finishImportWithMessage("Import failed: " + text);
-//	    break;
-//
-//	  case 'default':
-//        xModalMessage('FTP Inbox', "Unexpected import status message: " + tagName);
-//	    break;
-//	  }
-//	}
-//      } else {
-//        xModalMessage('FTP Inbox', "Invalid status response from server: expected one status message, received " + statusa.length);
-//      }
-//    } else {
-//        xModalMessage('FTP Inbox', "Unable to check import status (error " + req.status + "): " + req.responseText);
-//    }
-//  }
-//}
-//
-//
-//Inbox.prototype.doRefreshActions = function() {
-//  for (var i in this.refreshActions) {
-//    this.refreshActions[i]();
-//  }
-//}
-//
-//Inbox.prototype.refresh = function() {
-//  // If there's an import in progress, don't interrupt it.
-//  if (null == this.importing) {
-//    this.selfRefresh();
-//  }
-//  this.doRefreshActions();
-//}
-//
-//
-//Inbox.prototype.selfRefresh = function() {
-//  // Clear the inbox space, replace with a message
-//  while (null != this.div.firstChild) {
-//    this.div.removeChild(this.div.firstChild);
-//  }
-//  this.div.appendChild(document.createTextNode('Checking FTP inbox...'));
-//  this.loading = true;
-//
-//  // TODO: test for browser support?
-//  if (window.XMLHttpRequest) {
-//    this.listreq = new XMLHttpRequest();
-//  } else if (window.ActiveXObject) {
-//    this.listreq = new ActiveXObject("Microsoft.XMLHTTP");
-//  }
-//  this.listreq.open("GET", this.listURL, true);
-//  this.listreq.onreadystatechange = this.listCallback;
-//  this.listreq.send(null);
-//};
-//
-//
-//Inbox.prototype.addRefreshAction = function(handler) {
-//  this.refreshActions.push(handler);
-//}
-//
-//
-//Inbox.prototype.finishImportWithMessage = function(msg) {
-//  // Cancel the import monitor
-//  if (this.importInterval) {
-//    window.clearInterval(this.importInterval);
-//    this.importInterval = null;
-//  }
-//
-//  // Refresh any dependents first
-//  this.doRefreshActions();
-//
-//  var messageDiv = this.div.firstChild;
-//
-//  var message = document.createElement('p');
-//  message.appendChild(document.createTextNode(msg));
-//  messageDiv.appendChild(message);
-//
-//  var continueButton = document.createElement('input');
-//  continueButton.setAttribute('type', 'button');
-//  continueButton.setAttribute('value', 'Refresh FTP inbox');
-//
-//  var instance = this;
-//  continueButton.onclick = function() {
-//    instance.importing = null;
-//    instance.refresh();
-//  }
-//  messageDiv.appendChild(continueButton);
-//  messageDiv.scrollTop = messageDiv.scrollHeight;
-//}
diff --git a/src/main/webapp/scripts/lib/yamljs/Cakefile b/src/main/webapp/scripts/lib/yamljs/Cakefile
new file mode 100755
index 0000000000000000000000000000000000000000..0449803b394ba01faa42bf165cac5bc2a21a7e8a
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/Cakefile
@@ -0,0 +1,117 @@
+
+{exec, spawn}   = require 'child_process'
+fs              = require 'fs'
+path            = require 'path'
+esc             = (arg) -> (''+arg).replace(/(?=[^a-zA-Z0-9_.\/\-\x7F-\xFF\n])/gm, '\\').replace(/\n/g, "'\n'").replace(/^$/, "''")
+
+srcDir = path.normalize __dirname+'/src'
+libDir = path.normalize __dirname+'/lib'
+libDebugDir = path.normalize __dirname+'/lib/debug'
+distDir = path.normalize __dirname+'/dist'
+cliDir = path.normalize __dirname+'/cli'
+binDir = path.normalize __dirname+'/bin'
+specDir = path.normalize __dirname+'/test/spec'
+modulesDir = path.normalize __dirname+'/node_modules'
+
+task 'build', 'build project', ->
+
+    # Compile
+    do compile = ->
+        unless fs.existsSync libDir
+            fs.mkdirSync libDir
+        unless fs.existsSync libDir+'/Exception'
+            fs.mkdirSync libDir+'/Exception'
+        toCompile = 'Yaml Utils Unescaper Pattern Parser Inline Escaper Dumper Exception/ParseException Exception/DumpException'.split ' '
+        do compileOne = ->
+            name = toCompile.shift()
+            outputDir = (if '/' in name then libDir+'/Exception' else libDir)
+            exec 'coffee -b -o '+esc(outputDir)+' -c '+esc(srcDir+'/'+name+'.coffee'), (err, res) ->
+                if err then throw err
+
+                console.log "Compiled #{name}.js"
+                if toCompile.length
+                    compileOne()
+                else
+                    debugCompile()
+
+    # Debug compile
+    debugCompile = ->
+        unless fs.existsSync libDebugDir
+            fs.mkdirSync libDebugDir
+        unless fs.existsSync libDebugDir+'/Exception'
+            fs.mkdirSync libDebugDir+'/Exception'
+        toCompile = 'Yaml Utils Unescaper Pattern Parser Inline Escaper Dumper Exception/ParseException Exception/DumpException'.split ' '
+        do compileOne = ->
+            name = toCompile.shift()
+            outputDir = (if '/' in name then libDebugDir+'/Exception' else libDebugDir)
+            exec 'coffee -m -b -o '+esc(outputDir)+' -c '+esc(srcDir+'/'+name+'.coffee'), (err, res) ->
+                if err then throw err
+
+                console.log "Compiled #{name}.js (debug)"
+                if toCompile.length
+                    compileOne()
+                else
+                    browserify()
+
+    # Browserify
+    unless fs.existsSync distDir
+        fs.mkdirSync distDir
+    browserify = ->
+        exec 'browserify -t coffeeify --extension=".coffee" '+esc(srcDir+'/Yaml.coffee')+' > '+esc(distDir+'/yaml.js'), (err, res) ->
+            if err then throw err
+
+            console.log "Browserified yaml.js"
+            exec 'browserify --debug -t coffeeify --extension=".coffee" '+esc(srcDir+'/Yaml.coffee')+' > '+esc(distDir+'/yaml.debug.js'), (err, res) ->
+                if err then throw err
+
+                console.log "Browserified yaml.js (debug)"
+                minify()
+
+    # Minify
+    minify = ->
+        exec 'uglifyjs --mangle sort '+esc(distDir+'/yaml.js')+' > '+esc(distDir+'/yaml.min.js'), (err, res) ->
+            if err then throw err
+
+            console.log "Minified yaml.min.js"
+            compileSpec()
+
+    # Compile spec
+    compileSpec = ->
+        exec 'coffee -b -c '+esc(specDir+'/YamlSpec.coffee'), (err, res) ->
+            if err then throw err
+
+            console.log "Compiled YamlSpec.js"
+            compileCLI()
+
+    # Compile CLI
+    compileCLI = ->
+        unless fs.existsSync binDir
+            fs.mkdirSync binDir
+
+        # yaml2json
+        str = fs.readFileSync cliDir+'/yaml2json.js'
+        str = "#!/usr/bin/env node\n" + str
+        fs.writeFileSync binDir+'/yaml2json', str
+        fs.chmodSync binDir+'/yaml2json', '755'
+        console.log "Bundled yaml2json"
+
+        # json2yaml
+        str = fs.readFileSync cliDir+'/json2yaml.js'
+        str = "#!/usr/bin/env node\n" + str
+        fs.writeFileSync binDir+'/json2yaml', str
+        fs.chmodSync binDir+'/json2yaml', '755'
+        console.log "Bundled json2yaml"
+
+
+task 'test', 'test project', ->
+
+    # Test
+    spawn 'node', [modulesDir+'/jasmine-node/lib/jasmine-node/cli.js', '--verbose', '--coffee', specDir+'/YamlSpec.coffee'], stdio: "inherit"
+
+
+task 'doc', 'generate documentation', ->
+
+    # Generate
+    spawn 'codo', [srcDir], stdio: "inherit"
+
+
diff --git a/src/main/webapp/scripts/lib/yamljs/LICENSE b/src/main/webapp/scripts/lib/yamljs/LICENSE
new file mode 100755
index 0000000000000000000000000000000000000000..8adaf06b462fddd9d657be7af0d0c4e551227cc7
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2010 Jeremy Faivre
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/src/main/webapp/scripts/lib/yamljs/README.md b/src/main/webapp/scripts/lib/yamljs/README.md
new file mode 100755
index 0000000000000000000000000000000000000000..d3fae46a0523a48eac47cadec1b3c69a7b2932b6
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/README.md
@@ -0,0 +1,154 @@
+yaml.js
+=======
+
+![Build status](https://travis-ci.org/jeremyfa/yaml.js.svg?branch=develop)
+
+Standalone JavaScript YAML 1.2 Parser & Encoder. Works under node.js and all major browsers. Also brings command line YAML/JSON conversion tools.
+
+Mainly inspired from [Symfony Yaml Component](https://github.com/symfony/Yaml).
+
+How to use
+----------
+
+Import yaml.js in your html page:
+
+``` html
+<script type="text/javascript" src="yaml.js"></script>
+```
+
+Parse yaml string:
+
+``` js
+nativeObject = YAML.parse(yamlString);
+```
+
+Dump native object into yaml string:
+
+``` js
+yamlString = YAML.stringify(nativeObject[, inline /* @integer depth to start using inline notation at */[, spaces /* @integer number of spaces to use for indentation */] ]);
+```
+
+Load yaml file:
+
+``` js
+nativeObject = YAML.load('file.yml');
+```
+
+Load yaml file:
+
+``` js
+YAML.load('file.yml', function(result)
+{
+    nativeObject = result;
+});
+```
+
+Use with node.js
+----------------
+
+Install module:
+
+``` bash
+npm install yamljs
+```
+
+Use it:
+
+``` js
+YAML = require('yamljs');
+
+// parse YAML string
+nativeObject = YAML.parse(yamlString);
+
+// Generate YAML
+yamlString = YAML.stringify(nativeObject, 4);
+
+// Load yaml file using YAML.load
+nativeObject = YAML.load('myfile.yml');
+```
+
+Command line tools
+------------------
+
+You can enable the command line tools by installing yamljs as a global module:
+
+``` bash
+npm install -g yamljs
+```
+
+Then, two cli commands should become available: **yaml2json** and **json2yaml**. They let you convert YAML to JSON and JSON to YAML very easily.
+
+**yaml2json**
+
+```
+usage: yaml2json [-h] [-v] [-p] [-i INDENTATION] [-s] [-r] [-w] input
+
+Positional arguments:
+  input                 YAML file or directory containing YAML files.
+
+Optional arguments:
+  -h, --help            Show this help message and exit.
+  -v, --version         Show program's version number and exit.
+  -p, --pretty          Output pretty (indented) JSON.
+  -i INDENTATION, --indentation INDENTATION
+                        Number of space characters used to indent code (use 
+                        with --pretty, default: 2).
+  -s, --save            Save output inside JSON file(s) with the same name.
+  -r, --recursive       If the input is a directory, also find YAML files in 
+                        sub-directories recursively.
+  -w, --watch           Watch for changes.
+```
+
+**json2yaml**
+
+```
+usage: json2yaml [-h] [-v] [-d DEPTH] [-i INDENTATION] [-s] [-r] [-w] input
+
+Positional arguments:
+  input                 JSON file or directory containing JSON files.
+
+Optional arguments:
+  -h, --help            Show this help message and exit.
+  -v, --version         Show program's version number and exit.
+  -d DEPTH, --depth DEPTH
+                        Set minimum level of depth before generating inline 
+                        YAML (default: 2).
+  -i INDENTATION, --indentation INDENTATION
+                        Number of space characters used to indent code 
+                        (default: 2).
+  -s, --save            Save output inside YML file(s) with the same name.
+  -r, --recursive       If the input is a directory, also find JSON files in 
+                        sub-directories recursively.
+  -w, --watch           Watch for changes.
+```
+
+**examples**
+
+``` bash
+# Convert YAML to JSON and output resulting JSON on the console
+yaml2json myfile.yml
+
+# Store output inside a JSON file
+yaml2json myfile.yml > ouput.json
+
+# Output "pretty" (indented) JSON
+yaml2json myfile.yml --pretty
+
+# Save the output inside a file called myfile.json
+yaml2json myfile.yml --pretty --save
+
+# Watch a full directory and convert any YAML file into its JSON equivalent
+yaml2json mydirectory --pretty --save --recursive
+
+# Convert JSON to YAML and store output inside a JSON file
+json2yaml myfile.json > ouput.yml
+
+# Output YAML that will be inlined only after 8 levels of indentation
+json2yaml myfile.json --depth 8
+
+# Save the output inside a file called myfile.json with 4 spaces for each indentation
+json2yaml myfile.json --indentation 4
+
+# Watch a full directory and convert any JSON file into its YAML equivalent
+json2yaml mydirectory --pretty --save --recursive
+
diff --git a/src/main/webapp/scripts/lib/yamljs/bin/json2yaml b/src/main/webapp/scripts/lib/yamljs/bin/json2yaml
new file mode 100755
index 0000000000000000000000000000000000000000..f0a8791d7f940f649ed397f4a77da36f260a7e06
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/bin/json2yaml
@@ -0,0 +1,186 @@
+#!/usr/bin/env node
+
+/**
+ * yaml2json cli program
+ */
+ 
+var YAML = require('../lib/Yaml.js');
+
+var ArgumentParser = require('argparse').ArgumentParser;
+var cli = new ArgumentParser({
+    prog:           "json2yaml", 
+    version:        require('../package.json').version,
+    addHelp:        true
+});
+
+cli.addArgument(
+    ['-d', '--depth'],
+    {
+        action: 'store',
+        type:   'int', 
+        help:   'Set minimum level of depth before generating inline YAML (default: 2).'
+    }
+);
+
+cli.addArgument(
+    ['-i', '--indentation'],
+    {
+        action: 'store',
+        type:   'int', 
+        help:   'Number of space characters used to indent code (default: 2).',
+    }
+);
+
+cli.addArgument(
+    ['-s', '--save'],
+    {
+        help:   'Save output inside YML file(s) with the same name.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(
+    ['-r', '--recursive'],
+    {
+        help:   'If the input is a directory, also find JSON files in sub-directories recursively.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(
+    ['-w', '--watch'],
+    {
+        help:   'Watch for changes.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(['input'], {
+    help:   'JSON file or directory containing JSON files or - to read JSON from stdin.'
+});
+
+try {
+    var options = cli.parseArgs();
+    var path = require('path');
+    var fs   = require('fs');
+    var glob = require('glob');
+    
+    var rootPath = process.cwd();
+    var parsePath = function(input) {
+        if (input == '-') return '-';
+        var output;
+        if (!(input != null)) {
+            return rootPath;
+        }
+        output = path.normalize(input);
+        if (output.length === 0) {
+            return rootPath;
+        }
+        if (output.charAt(0) !== '/') {
+            output = path.normalize(rootPath + '/./' + output);
+        }
+        if (output.length > 1 && output.charAt(output.length - 1) === '/') {
+            return output.substr(0, output.length - 1);
+        }
+        return output;
+    };
+
+    // Find files
+    var findFiles = function(input) {
+        if (input != '-' && input != null) {
+            var isDirectory = fs.statSync(input).isDirectory();
+            var files = [];
+
+            if (!isDirectory) {
+                files.push(input);
+            }
+            else {
+                if (options.recursive) {
+                    files = files.concat(glob.sync(input+'/**/*.json'));
+                }
+                else {
+                    files = files.concat(glob.sync(input+'/*.json'));
+                }
+            }
+        
+            return files;
+        }
+        return null;
+    };
+
+    // Convert to JSON
+    var convertToYAML = function(input, inline, save, spaces, str) {
+        var yaml;
+        if (inline == null) inline = 2;
+        if (spaces == null) spaces = 2;
+        
+        if (str == null) {
+            str = ''+fs.readFileSync(input);
+        }
+        yaml = YAML.dump(JSON.parse(str), inline, spaces);
+    
+        if (!save || input == null) {
+            // Ouput result
+            process.stdout.write(yaml);
+        }
+        else {
+            var output;
+            if (input.substring(input.length-5) == '.json') {
+                output = input.substr(0, input.length-5) + '.yaml';
+            }
+            else {
+                output = input + '.yaml';
+            }
+        
+            // Write file
+            var file = fs.openSync(output, 'w+');
+            fs.writeSync(file, yaml);
+            fs.closeSync(file);
+            process.stdout.write("saved "+output+"\n");
+        }
+    };
+
+    var input = parsePath(options.input);
+    var mtimes = [];
+
+    var runCommand = function() {
+        try {
+            var files = findFiles(input);
+            if (files != null) {
+                var len = files.length;
+                for (var i = 0; i < len; i++) {
+                    var file = files[i];
+                    var stat = fs.statSync(file);
+                    var time = stat.mtime.getTime();
+                    if (!stat.isDirectory()) {
+                        if (!mtimes[file] || mtimes[file] < time) {
+                            mtimes[file] = time;
+                            convertToYAML(file, options.depth, options.save, options.indentation);
+                        }
+                    }
+                }
+            } else {
+                // Read from STDIN
+                var stdin = process.openStdin();
+                var data = "";
+                stdin.on('data', function(chunk) {
+                    data += chunk;
+                });
+                stdin.on('end', function() {
+                    convertToYAML(null, options.depth, options.save, options.indentation, data);
+                });
+            }
+        } catch (e) {
+            process.stderr.write((e.message ? e.message : e)+"\n");
+        }
+    };
+
+    if (!options.watch) {
+        runCommand();
+    } else {
+        runCommand();
+        setInterval(runCommand, 1000);
+    } 
+} catch (e) {
+    process.stderr.write((e.message ? e.message : e)+"\n");
+}
diff --git a/src/main/webapp/scripts/lib/yamljs/bin/yaml2json b/src/main/webapp/scripts/lib/yamljs/bin/yaml2json
new file mode 100755
index 0000000000000000000000000000000000000000..550230c71157c6708ad87a6d25f142e8d86ff2b2
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/bin/yaml2json
@@ -0,0 +1,200 @@
+#!/usr/bin/env node
+
+/**
+ * yaml2json cli program
+ */
+ 
+var YAML = require('../lib/Yaml.js');
+
+var ArgumentParser = require('argparse').ArgumentParser;
+var cli = new ArgumentParser({
+    prog:           "yaml2json", 
+    version:        require('../package.json').version,
+    addHelp:        true
+});
+
+cli.addArgument(
+    ['-p', '--pretty'],
+    {
+        help:   'Output pretty (indented) JSON.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(
+    ['-i', '--indentation'],
+    {
+        action: 'store',
+        type:   'int', 
+        help:   'Number of space characters used to indent code (use with --pretty, default: 2).',
+    }
+);
+
+cli.addArgument(
+    ['-s', '--save'],
+    {
+        help:   'Save output inside JSON file(s) with the same name.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(
+    ['-r', '--recursive'],
+    {
+        help:   'If the input is a directory, also find YAML files in sub-directories recursively.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(
+    ['-w', '--watch'],
+    {
+        help:   'Watch for changes.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(['input'], {
+    help:   'YAML file or directory containing YAML files or - to read YAML from stdin.'
+});
+
+try {
+    var options = cli.parseArgs();
+    var path = require('path');
+    var fs   = require('fs');
+    var glob = require('glob');
+    
+    var rootPath = process.cwd();
+    var parsePath = function(input) {
+        if (input == '-') return '-';
+        var output;
+        if (!(input != null)) {
+            return rootPath;
+        }
+        output = path.normalize(input);
+        if (output.length === 0) {
+            return rootPath;
+        }
+        if (output.charAt(0) !== '/') {
+            output = path.normalize(rootPath + '/./' + output);
+        }
+        if (output.length > 1 && output.charAt(output.length - 1) === '/') {
+            return output.substr(0, output.length - 1);
+        }
+        return output;
+    };
+
+    // Find files
+    var findFiles = function(input) {
+        if (input != '-' && input != null) {
+            var isDirectory = fs.statSync(input).isDirectory();
+            var files = [];
+
+            if (!isDirectory) {
+                files.push(input);
+            }
+            else {
+                if (options.recursive) {
+                    files = files.concat(glob.sync(input+'/**/*.yml'));
+                    files = files.concat(glob.sync(input+'/**/*.yaml'));
+                }
+                else {
+                    files = files.concat(glob.sync(input+'/*.yml'));
+                    files = files.concat(glob.sync(input+'/*.yaml'));
+                }
+            }
+        
+            return files;
+        }
+        return null;
+    };
+
+    // Convert to JSON
+    var convertToJSON = function(input, pretty, save, spaces, str) {
+        var json;
+        if (spaces == null) spaces = 2;
+        if (str != null) {
+            if (pretty) {
+                json = JSON.stringify(YAML.parse(str), null, spaces);
+            }
+            else {
+                json = JSON.stringify(YAML.parse(str));
+            }
+        } else {
+            if (pretty) {
+                json = JSON.stringify(YAML.parseFile(input), null, spaces);
+            }
+            else {
+                json = JSON.stringify(YAML.parseFile(input));
+            }
+        }
+    
+        if (!save || input == null) {
+            // Ouput result
+            process.stdout.write(json+"\n");
+        }
+        else {
+            var output;
+            if (input.substring(input.length-4) == '.yml') {
+                output = input.substr(0, input.length-4) + '.json';
+            }
+            else if (input.substring(input.length-5) == '.yaml') {
+                output = input.substr(0, input.length-5) + '.json';
+            }
+            else {
+                output = input + '.json';
+            }
+        
+            // Write file
+            var file = fs.openSync(output, 'w+');
+            fs.writeSync(file, json);
+            fs.closeSync(file);
+            process.stdout.write("saved "+output+"\n");
+        }
+    };
+
+    var input = parsePath(options.input);
+    var mtimes = [];
+
+    var runCommand = function() {
+        try {
+            var files = findFiles(input);
+            if (files != null) {
+                var len = files.length;
+
+                for (var i = 0; i < len; i++) {
+                    var file = files[i];
+                    var stat = fs.statSync(file);
+                    var time = stat.mtime.getTime();
+                    if (!stat.isDirectory()) {
+                        if (!mtimes[file] || mtimes[file] < time) {
+                            mtimes[file] = time;
+                            convertToJSON(file, options.pretty, options.save, options.indentation);
+                        }
+                    }
+                }
+            } else {
+                // Read from STDIN
+                var stdin = process.openStdin();
+                var data = "";
+                stdin.on('data', function(chunk) {
+                    data += chunk;
+                });
+                stdin.on('end', function() {
+                    convertToJSON(null, options.pretty, options.save, options.indentation, data);
+                });
+            }
+        } catch (e) {
+            process.stderr.write((e.message ? e.message : e)+"\n");
+        }
+    };
+
+    if (!options.watch) {
+        runCommand();
+    } else {
+        runCommand();
+        setInterval(runCommand, 1000);
+    } 
+} catch (e) {
+    process.stderr.write((e.message ? e.message : e)+"\n");
+}
diff --git a/src/main/webapp/scripts/lib/yamljs/bower.json b/src/main/webapp/scripts/lib/yamljs/bower.json
new file mode 100755
index 0000000000000000000000000000000000000000..7383648345eaf6735dd56c138389b8a450b5459c
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/bower.json
@@ -0,0 +1,19 @@
+{
+  "name": "yaml.js",
+  "main": "dist/yaml.js",
+  "version": "0.2.3",
+  "homepage": "https://github.com/jeremyfa/yaml.js",
+  "authors": [
+    "Jeremy Faivre <contact@jeremyfa.com>"
+  ],
+  "description": "Standalone JavaScript YAML 1.2 Parser & Encoder. Works under node.js and all major browsers. Also brings command line YAML/JSON conversion tools.",
+  "keywords": [
+    "yaml"
+  ],
+  "license": "MIT",
+  "ignore": [
+    "**/.*",
+    "node_modules",
+    "bower_components"
+  ]
+}
diff --git a/src/main/webapp/scripts/lib/yamljs/cli/json2yaml.js b/src/main/webapp/scripts/lib/yamljs/cli/json2yaml.js
new file mode 100755
index 0000000000000000000000000000000000000000..4c849b250e257a770149db9949f72d0d193d482c
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/cli/json2yaml.js
@@ -0,0 +1,185 @@
+
+/**
+ * yaml2json cli program
+ */
+ 
+var YAML = require('../lib/Yaml.js');
+
+var ArgumentParser = require('argparse').ArgumentParser;
+var cli = new ArgumentParser({
+    prog:           "json2yaml", 
+    version:        require('../package.json').version,
+    addHelp:        true
+});
+
+cli.addArgument(
+    ['-d', '--depth'],
+    {
+        action: 'store',
+        type:   'int', 
+        help:   'Set minimum level of depth before generating inline YAML (default: 2).'
+    }
+);
+
+cli.addArgument(
+    ['-i', '--indentation'],
+    {
+        action: 'store',
+        type:   'int', 
+        help:   'Number of space characters used to indent code (default: 2).',
+    }
+);
+
+cli.addArgument(
+    ['-s', '--save'],
+    {
+        help:   'Save output inside YML file(s) with the same name.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(
+    ['-r', '--recursive'],
+    {
+        help:   'If the input is a directory, also find JSON files in sub-directories recursively.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(
+    ['-w', '--watch'],
+    {
+        help:   'Watch for changes.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(['input'], {
+    help:   'JSON file or directory containing JSON files or - to read JSON from stdin.'
+});
+
+try {
+    var options = cli.parseArgs();
+    var path = require('path');
+    var fs   = require('fs');
+    var glob = require('glob');
+    
+    var rootPath = process.cwd();
+    var parsePath = function(input) {
+        if (input == '-') return '-';
+        var output;
+        if (!(input != null)) {
+            return rootPath;
+        }
+        output = path.normalize(input);
+        if (output.length === 0) {
+            return rootPath;
+        }
+        if (output.charAt(0) !== '/') {
+            output = path.normalize(rootPath + '/./' + output);
+        }
+        if (output.length > 1 && output.charAt(output.length - 1) === '/') {
+            return output.substr(0, output.length - 1);
+        }
+        return output;
+    };
+
+    // Find files
+    var findFiles = function(input) {
+        if (input != '-' && input != null) {
+            var isDirectory = fs.statSync(input).isDirectory();
+            var files = [];
+
+            if (!isDirectory) {
+                files.push(input);
+            }
+            else {
+                if (options.recursive) {
+                    files = files.concat(glob.sync(input+'/**/*.json'));
+                }
+                else {
+                    files = files.concat(glob.sync(input+'/*.json'));
+                }
+            }
+        
+            return files;
+        }
+        return null;
+    };
+
+    // Convert to JSON
+    var convertToYAML = function(input, inline, save, spaces, str) {
+        var yaml;
+        if (inline == null) inline = 2;
+        if (spaces == null) spaces = 2;
+        
+        if (str == null) {
+            str = ''+fs.readFileSync(input);
+        }
+        yaml = YAML.dump(JSON.parse(str), inline, spaces);
+    
+        if (!save || input == null) {
+            // Ouput result
+            process.stdout.write(yaml);
+        }
+        else {
+            var output;
+            if (input.substring(input.length-5) == '.json') {
+                output = input.substr(0, input.length-5) + '.yaml';
+            }
+            else {
+                output = input + '.yaml';
+            }
+        
+            // Write file
+            var file = fs.openSync(output, 'w+');
+            fs.writeSync(file, yaml);
+            fs.closeSync(file);
+            process.stdout.write("saved "+output+"\n");
+        }
+    };
+
+    var input = parsePath(options.input);
+    var mtimes = [];
+
+    var runCommand = function() {
+        try {
+            var files = findFiles(input);
+            if (files != null) {
+                var len = files.length;
+                for (var i = 0; i < len; i++) {
+                    var file = files[i];
+                    var stat = fs.statSync(file);
+                    var time = stat.mtime.getTime();
+                    if (!stat.isDirectory()) {
+                        if (!mtimes[file] || mtimes[file] < time) {
+                            mtimes[file] = time;
+                            convertToYAML(file, options.depth, options.save, options.indentation);
+                        }
+                    }
+                }
+            } else {
+                // Read from STDIN
+                var stdin = process.openStdin();
+                var data = "";
+                stdin.on('data', function(chunk) {
+                    data += chunk;
+                });
+                stdin.on('end', function() {
+                    convertToYAML(null, options.depth, options.save, options.indentation, data);
+                });
+            }
+        } catch (e) {
+            process.stderr.write((e.message ? e.message : e)+"\n");
+        }
+    };
+
+    if (!options.watch) {
+        runCommand();
+    } else {
+        runCommand();
+        setInterval(runCommand, 1000);
+    } 
+} catch (e) {
+    process.stderr.write((e.message ? e.message : e)+"\n");
+}
diff --git a/src/main/webapp/scripts/lib/yamljs/cli/yaml2json.js b/src/main/webapp/scripts/lib/yamljs/cli/yaml2json.js
new file mode 100755
index 0000000000000000000000000000000000000000..662201c8cf1e3302c5e20a26e809f0f9cfeedb3d
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/cli/yaml2json.js
@@ -0,0 +1,199 @@
+
+/**
+ * yaml2json cli program
+ */
+ 
+var YAML = require('../lib/Yaml.js');
+
+var ArgumentParser = require('argparse').ArgumentParser;
+var cli = new ArgumentParser({
+    prog:           "yaml2json", 
+    version:        require('../package.json').version,
+    addHelp:        true
+});
+
+cli.addArgument(
+    ['-p', '--pretty'],
+    {
+        help:   'Output pretty (indented) JSON.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(
+    ['-i', '--indentation'],
+    {
+        action: 'store',
+        type:   'int', 
+        help:   'Number of space characters used to indent code (use with --pretty, default: 2).',
+    }
+);
+
+cli.addArgument(
+    ['-s', '--save'],
+    {
+        help:   'Save output inside JSON file(s) with the same name.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(
+    ['-r', '--recursive'],
+    {
+        help:   'If the input is a directory, also find YAML files in sub-directories recursively.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(
+    ['-w', '--watch'],
+    {
+        help:   'Watch for changes.',
+        action: 'storeTrue'
+    }
+);
+
+cli.addArgument(['input'], {
+    help:   'YAML file or directory containing YAML files or - to read YAML from stdin.'
+});
+
+try {
+    var options = cli.parseArgs();
+    var path = require('path');
+    var fs   = require('fs');
+    var glob = require('glob');
+    
+    var rootPath = process.cwd();
+    var parsePath = function(input) {
+        if (input == '-') return '-';
+        var output;
+        if (!(input != null)) {
+            return rootPath;
+        }
+        output = path.normalize(input);
+        if (output.length === 0) {
+            return rootPath;
+        }
+        if (output.charAt(0) !== '/') {
+            output = path.normalize(rootPath + '/./' + output);
+        }
+        if (output.length > 1 && output.charAt(output.length - 1) === '/') {
+            return output.substr(0, output.length - 1);
+        }
+        return output;
+    };
+
+    // Find files
+    var findFiles = function(input) {
+        if (input != '-' && input != null) {
+            var isDirectory = fs.statSync(input).isDirectory();
+            var files = [];
+
+            if (!isDirectory) {
+                files.push(input);
+            }
+            else {
+                if (options.recursive) {
+                    files = files.concat(glob.sync(input+'/**/*.yml'));
+                    files = files.concat(glob.sync(input+'/**/*.yaml'));
+                }
+                else {
+                    files = files.concat(glob.sync(input+'/*.yml'));
+                    files = files.concat(glob.sync(input+'/*.yaml'));
+                }
+            }
+        
+            return files;
+        }
+        return null;
+    };
+
+    // Convert to JSON
+    var convertToJSON = function(input, pretty, save, spaces, str) {
+        var json;
+        if (spaces == null) spaces = 2;
+        if (str != null) {
+            if (pretty) {
+                json = JSON.stringify(YAML.parse(str), null, spaces);
+            }
+            else {
+                json = JSON.stringify(YAML.parse(str));
+            }
+        } else {
+            if (pretty) {
+                json = JSON.stringify(YAML.parseFile(input), null, spaces);
+            }
+            else {
+                json = JSON.stringify(YAML.parseFile(input));
+            }
+        }
+    
+        if (!save || input == null) {
+            // Ouput result
+            process.stdout.write(json+"\n");
+        }
+        else {
+            var output;
+            if (input.substring(input.length-4) == '.yml') {
+                output = input.substr(0, input.length-4) + '.json';
+            }
+            else if (input.substring(input.length-5) == '.yaml') {
+                output = input.substr(0, input.length-5) + '.json';
+            }
+            else {
+                output = input + '.json';
+            }
+        
+            // Write file
+            var file = fs.openSync(output, 'w+');
+            fs.writeSync(file, json);
+            fs.closeSync(file);
+            process.stdout.write("saved "+output+"\n");
+        }
+    };
+
+    var input = parsePath(options.input);
+    var mtimes = [];
+
+    var runCommand = function() {
+        try {
+            var files = findFiles(input);
+            if (files != null) {
+                var len = files.length;
+
+                for (var i = 0; i < len; i++) {
+                    var file = files[i];
+                    var stat = fs.statSync(file);
+                    var time = stat.mtime.getTime();
+                    if (!stat.isDirectory()) {
+                        if (!mtimes[file] || mtimes[file] < time) {
+                            mtimes[file] = time;
+                            convertToJSON(file, options.pretty, options.save, options.indentation);
+                        }
+                    }
+                }
+            } else {
+                // Read from STDIN
+                var stdin = process.openStdin();
+                var data = "";
+                stdin.on('data', function(chunk) {
+                    data += chunk;
+                });
+                stdin.on('end', function() {
+                    convertToJSON(null, options.pretty, options.save, options.indentation, data);
+                });
+            }
+        } catch (e) {
+            process.stderr.write((e.message ? e.message : e)+"\n");
+        }
+    };
+
+    if (!options.watch) {
+        runCommand();
+    } else {
+        runCommand();
+        setInterval(runCommand, 1000);
+    } 
+} catch (e) {
+    process.stderr.write((e.message ? e.message : e)+"\n");
+}
diff --git a/src/main/webapp/scripts/lib/yamljs/demo/demo.html b/src/main/webapp/scripts/lib/yamljs/demo/demo.html
new file mode 100755
index 0000000000000000000000000000000000000000..101ed53b22601d6fac55c75e3374ef6d5b4b9293
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/demo/demo.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+	<style type="text/css">
+	/*
+	Copyright (c) 2010, Yahoo! Inc. All rights reserved.
+	Code licensed under the BSD License:
+	http://developer.yahoo.com/yui/license.html
+	version: 3.2.0
+	build: 2676
+	*/
+	html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:text-top;}sub{vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}
+	
+	/*
+	 * Custom styles
+	 */
+	body {
+		width: 100%;
+		overflow: hidden;
+	}
+	#parse {
+		border: none;
+		background-color: white;
+		color: black;
+		z-index: 3;
+		position: absolute;
+		right: 50%;
+		top: 0;
+		width: 100px;
+	}
+	#yaml {
+		color: white;
+		background-color: black;
+		font-family: "Courier New";
+		font-size: 14px;
+		width: 50%;
+		border: none;
+		position: absolute;
+		top: 0;
+		left: 0;
+		z-index: 1;
+		height: 100%;
+	}
+	#result {
+		color: black;
+		background-color: white;
+		font-family: "Courier New";
+		font-size: 12px;
+		width: 50%;		
+		border: none;
+		position: absolute;
+		top: 0;
+		left: 50%;
+		overflow: auto;
+		z-index: 2;
+		height: 100%;
+        vertical-align: top;
+        overflow: auto;
+	}
+	#tests {
+		width: 50%;		
+		border: none;
+		position: absolute;
+		top: 0;
+		left: 50%;
+		z-index: 2;
+	}
+	</style>
+	
+	<!-- standalone yaml.js library -->
+	<script type="text/javascript" src="../dist/yaml.debug.js"></script>
+		
+	<title>yaml.js demo</title>
+</head>
+
+<body>
+<form action="" onsubmit="return false;">
+	<textarea name="yaml" id="yaml" cols="70" rows="20">--- !clarkevans.com/^invoice
+invoice: 34843
+date   : 2001-01-23
+bill-to: &amp;id001
+    given  : Chris
+    family : Dumars
+    address:
+        lines: |
+            458 Walkman Dr.
+            Suite #292
+        city    : Royal Oak
+        state   : MI
+        postal  : 48046
+ship-to: *id001
+product:
+    - sku         : &quot;BL394D&quot;
+      quantity    : 4
+      description : Basketball
+      price       : 450.00
+    - sku         : BL4438H
+      quantity    : 1
+      description : Super Hoop
+      price       : 2392.00
+tax  : 251.42
+total: 4443.52
+comments: &gt;
+    Late afternoon is best.
+    Backup contact is Nancy
+    Billsmer @ 338-4338.
+</textarea>
+<input type="button" id="parse" name="parse" value="Parse &raquo;" onclick="document.getElementById('result').innerHTML='<pre>'+JSON.stringify(YAML.parse(document.getElementById('yaml').value), null, 4)+'</pre>'" />
+<div id="result"></div>
+
+</form>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/webapp/scripts/lib/yamljs/dist/yaml.debug.js b/src/main/webapp/scripts/lib/yamljs/dist/yaml.debug.js
new file mode 100755
index 0000000000000000000000000000000000000000..a6d2d4e660ecddc9112689ca668e69cc53c617ba
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/dist/yaml.debug.js
@@ -0,0 +1,1860 @@
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+var Dumper, Inline, Utils;
+
+Utils = require('./Utils');
+
+Inline = require('./Inline');
+
+Dumper = (function() {
+  function Dumper() {}
+
+  Dumper.indentation = 4;
+
+  Dumper.prototype.dump = function(input, inline, indent, exceptionOnInvalidType, objectEncoder) {
+    var i, key, len, output, prefix, value, willBeInlined;
+    if (inline == null) {
+      inline = 0;
+    }
+    if (indent == null) {
+      indent = 0;
+    }
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectEncoder == null) {
+      objectEncoder = null;
+    }
+    output = '';
+    prefix = (indent ? Utils.strRepeat(' ', indent) : '');
+    if (inline <= 0 || typeof input !== 'object' || input instanceof Date || Utils.isEmpty(input)) {
+      output += prefix + Inline.dump(input, exceptionOnInvalidType, objectEncoder);
+    } else {
+      if (input instanceof Array) {
+        for (i = 0, len = input.length; i < len; i++) {
+          value = input[i];
+          willBeInlined = inline - 1 <= 0 || typeof value !== 'object' || Utils.isEmpty(value);
+          output += prefix + '-' + (willBeInlined ? ' ' : "\n") + this.dump(value, inline - 1, (willBeInlined ? 0 : indent + this.indentation), exceptionOnInvalidType, objectEncoder) + (willBeInlined ? "\n" : '');
+        }
+      } else {
+        for (key in input) {
+          value = input[key];
+          willBeInlined = inline - 1 <= 0 || typeof value !== 'object' || Utils.isEmpty(value);
+          output += prefix + Inline.dump(key, exceptionOnInvalidType, objectEncoder) + ':' + (willBeInlined ? ' ' : "\n") + this.dump(value, inline - 1, (willBeInlined ? 0 : indent + this.indentation), exceptionOnInvalidType, objectEncoder) + (willBeInlined ? "\n" : '');
+        }
+      }
+    }
+    return output;
+  };
+
+  return Dumper;
+
+})();
+
+module.exports = Dumper;
+
+
+},{"./Inline":5,"./Utils":9}],2:[function(require,module,exports){
+var Escaper, Pattern;
+
+Pattern = require('./Pattern');
+
+Escaper = (function() {
+  var ch;
+
+  function Escaper() {}
+
+  Escaper.LIST_ESCAPEES = ['\\', '\\\\', '\\"', '"', "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", (ch = String.fromCharCode)(0x0085), ch(0x00A0), ch(0x2028), ch(0x2029)];
+
+  Escaper.LIST_ESCAPED = ['\\\\', '\\"', '\\"', '\\"', "\\0", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\a", "\\b", "\\t", "\\n", "\\v", "\\f", "\\r", "\\x0e", "\\x0f", "\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17", "\\x18", "\\x19", "\\x1a", "\\e", "\\x1c", "\\x1d", "\\x1e", "\\x1f", "\\N", "\\_", "\\L", "\\P"];
+
+  Escaper.MAPPING_ESCAPEES_TO_ESCAPED = (function() {
+    var i, j, mapping, ref;
+    mapping = {};
+    for (i = j = 0, ref = Escaper.LIST_ESCAPEES.length; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
+      mapping[Escaper.LIST_ESCAPEES[i]] = Escaper.LIST_ESCAPED[i];
+    }
+    return mapping;
+  })();
+
+  Escaper.PATTERN_CHARACTERS_TO_ESCAPE = new Pattern('[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9');
+
+  Escaper.PATTERN_MAPPING_ESCAPEES = new Pattern(Escaper.LIST_ESCAPEES.join('|').split('\\').join('\\\\'));
+
+  Escaper.PATTERN_SINGLE_QUOTING = new Pattern('[\\s\'":{}[\\],&*#?]|^[-?|<>=!%@`]');
+
+  Escaper.requiresDoubleQuoting = function(value) {
+    return this.PATTERN_CHARACTERS_TO_ESCAPE.test(value);
+  };
+
+  Escaper.escapeWithDoubleQuotes = function(value) {
+    var result;
+    result = this.PATTERN_MAPPING_ESCAPEES.replace(value, (function(_this) {
+      return function(str) {
+        return _this.MAPPING_ESCAPEES_TO_ESCAPED[str];
+      };
+    })(this));
+    return '"' + result + '"';
+  };
+
+  Escaper.requiresSingleQuoting = function(value) {
+    return this.PATTERN_SINGLE_QUOTING.test(value);
+  };
+
+  Escaper.escapeWithSingleQuotes = function(value) {
+    return "'" + value.replace(/'/g, "''") + "'";
+  };
+
+  return Escaper;
+
+})();
+
+module.exports = Escaper;
+
+
+},{"./Pattern":7}],3:[function(require,module,exports){
+var DumpException,
+  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+  hasProp = {}.hasOwnProperty;
+
+DumpException = (function(superClass) {
+  extend(DumpException, superClass);
+
+  function DumpException(message, parsedLine, snippet) {
+    this.message = message;
+    this.parsedLine = parsedLine;
+    this.snippet = snippet;
+  }
+
+  DumpException.prototype.toString = function() {
+    if ((this.parsedLine != null) && (this.snippet != null)) {
+      return '<DumpException> ' + this.message + ' (line ' + this.parsedLine + ': \'' + this.snippet + '\')';
+    } else {
+      return '<DumpException> ' + this.message;
+    }
+  };
+
+  return DumpException;
+
+})(Error);
+
+module.exports = DumpException;
+
+
+},{}],4:[function(require,module,exports){
+var ParseException,
+  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+  hasProp = {}.hasOwnProperty;
+
+ParseException = (function(superClass) {
+  extend(ParseException, superClass);
+
+  function ParseException(message, parsedLine, snippet) {
+    this.message = message;
+    this.parsedLine = parsedLine;
+    this.snippet = snippet;
+  }
+
+  ParseException.prototype.toString = function() {
+    if ((this.parsedLine != null) && (this.snippet != null)) {
+      return '<ParseException> ' + this.message + ' (line ' + this.parsedLine + ': \'' + this.snippet + '\')';
+    } else {
+      return '<ParseException> ' + this.message;
+    }
+  };
+
+  return ParseException;
+
+})(Error);
+
+module.exports = ParseException;
+
+
+},{}],5:[function(require,module,exports){
+var DumpException, Escaper, Inline, ParseException, Pattern, Unescaper, Utils,
+  indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+Pattern = require('./Pattern');
+
+Unescaper = require('./Unescaper');
+
+Escaper = require('./Escaper');
+
+Utils = require('./Utils');
+
+ParseException = require('./Exception/ParseException');
+
+DumpException = require('./Exception/DumpException');
+
+Inline = (function() {
+  function Inline() {}
+
+  Inline.REGEX_QUOTED_STRING = '(?:"(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\']*(?:\'\'[^\']*)*)\')';
+
+  Inline.PATTERN_TRAILING_COMMENTS = new Pattern('^\\s*#.*$');
+
+  Inline.PATTERN_QUOTED_SCALAR = new Pattern('^' + Inline.REGEX_QUOTED_STRING);
+
+  Inline.PATTERN_THOUSAND_NUMERIC_SCALAR = new Pattern('^(-|\\+)?[0-9,]+(\\.[0-9]+)?$');
+
+  Inline.PATTERN_SCALAR_BY_DELIMITERS = {};
+
+  Inline.settings = {};
+
+  Inline.configure = function(exceptionOnInvalidType, objectDecoder) {
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = null;
+    }
+    if (objectDecoder == null) {
+      objectDecoder = null;
+    }
+    this.settings.exceptionOnInvalidType = exceptionOnInvalidType;
+    this.settings.objectDecoder = objectDecoder;
+  };
+
+  Inline.parse = function(value, exceptionOnInvalidType, objectDecoder) {
+    var context, result;
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectDecoder == null) {
+      objectDecoder = null;
+    }
+    this.settings.exceptionOnInvalidType = exceptionOnInvalidType;
+    this.settings.objectDecoder = objectDecoder;
+    if (value == null) {
+      return '';
+    }
+    value = Utils.trim(value);
+    if (0 === value.length) {
+      return '';
+    }
+    context = {
+      exceptionOnInvalidType: exceptionOnInvalidType,
+      objectDecoder: objectDecoder,
+      i: 0
+    };
+    switch (value.charAt(0)) {
+      case '[':
+        result = this.parseSequence(value, context);
+        ++context.i;
+        break;
+      case '{':
+        result = this.parseMapping(value, context);
+        ++context.i;
+        break;
+      default:
+        result = this.parseScalar(value, null, ['"', "'"], context);
+    }
+    if (this.PATTERN_TRAILING_COMMENTS.replace(value.slice(context.i), '') !== '') {
+      throw new ParseException('Unexpected characters near "' + value.slice(context.i) + '".');
+    }
+    return result;
+  };
+
+  Inline.dump = function(value, exceptionOnInvalidType, objectEncoder) {
+    var ref, result, type;
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectEncoder == null) {
+      objectEncoder = null;
+    }
+    if (value == null) {
+      return 'null';
+    }
+    type = typeof value;
+    if (type === 'object') {
+      if (value instanceof Date) {
+        return value.toISOString();
+      } else if (objectEncoder != null) {
+        result = objectEncoder(value);
+        if (typeof result === 'string' || (result != null)) {
+          return result;
+        }
+      }
+      return this.dumpObject(value);
+    }
+    if (type === 'boolean') {
+      return (value ? 'true' : 'false');
+    }
+    if (Utils.isDigits(value)) {
+      return (type === 'string' ? "'" + value + "'" : String(parseInt(value)));
+    }
+    if (Utils.isNumeric(value)) {
+      return (type === 'string' ? "'" + value + "'" : String(parseFloat(value)));
+    }
+    if (type === 'number') {
+      return (value === Infinity ? '.Inf' : (value === -Infinity ? '-.Inf' : (isNaN(value) ? '.NaN' : value)));
+    }
+    if (Escaper.requiresDoubleQuoting(value)) {
+      return Escaper.escapeWithDoubleQuotes(value);
+    }
+    if (Escaper.requiresSingleQuoting(value)) {
+      return Escaper.escapeWithSingleQuotes(value);
+    }
+    if ('' === value) {
+      return '""';
+    }
+    if (Utils.PATTERN_DATE.test(value)) {
+      return "'" + value + "'";
+    }
+    if ((ref = value.toLowerCase()) === 'null' || ref === '~' || ref === 'true' || ref === 'false') {
+      return "'" + value + "'";
+    }
+    return value;
+  };
+
+  Inline.dumpObject = function(value, exceptionOnInvalidType, objectSupport) {
+    var j, key, len1, output, val;
+    if (objectSupport == null) {
+      objectSupport = null;
+    }
+    if (value instanceof Array) {
+      output = [];
+      for (j = 0, len1 = value.length; j < len1; j++) {
+        val = value[j];
+        output.push(this.dump(val));
+      }
+      return '[' + output.join(', ') + ']';
+    } else {
+      output = [];
+      for (key in value) {
+        val = value[key];
+        output.push(this.dump(key) + ': ' + this.dump(val));
+      }
+      return '{' + output.join(', ') + '}';
+    }
+  };
+
+  Inline.parseScalar = function(scalar, delimiters, stringDelimiters, context, evaluate) {
+    var i, joinedDelimiters, match, output, pattern, ref, ref1, strpos, tmp;
+    if (delimiters == null) {
+      delimiters = null;
+    }
+    if (stringDelimiters == null) {
+      stringDelimiters = ['"', "'"];
+    }
+    if (context == null) {
+      context = null;
+    }
+    if (evaluate == null) {
+      evaluate = true;
+    }
+    if (context == null) {
+      context = {
+        exceptionOnInvalidType: this.settings.exceptionOnInvalidType,
+        objectDecoder: this.settings.objectDecoder,
+        i: 0
+      };
+    }
+    i = context.i;
+    if (ref = scalar.charAt(i), indexOf.call(stringDelimiters, ref) >= 0) {
+      output = this.parseQuotedScalar(scalar, context);
+      i = context.i;
+      if (delimiters != null) {
+        tmp = Utils.ltrim(scalar.slice(i), ' ');
+        if (!(ref1 = tmp.charAt(0), indexOf.call(delimiters, ref1) >= 0)) {
+          throw new ParseException('Unexpected characters (' + scalar.slice(i) + ').');
+        }
+      }
+    } else {
+      if (!delimiters) {
+        output = scalar.slice(i);
+        i += output.length;
+        strpos = output.indexOf(' #');
+        if (strpos !== -1) {
+          output = Utils.rtrim(output.slice(0, strpos));
+        }
+      } else {
+        joinedDelimiters = delimiters.join('|');
+        pattern = this.PATTERN_SCALAR_BY_DELIMITERS[joinedDelimiters];
+        if (pattern == null) {
+          pattern = new Pattern('^(.+?)(' + joinedDelimiters + ')');
+          this.PATTERN_SCALAR_BY_DELIMITERS[joinedDelimiters] = pattern;
+        }
+        if (match = pattern.exec(scalar.slice(i))) {
+          output = match[1];
+          i += output.length;
+        } else {
+          throw new ParseException('Malformed inline YAML string (' + scalar + ').');
+        }
+      }
+      if (evaluate) {
+        output = this.evaluateScalar(output, context);
+      }
+    }
+    context.i = i;
+    return output;
+  };
+
+  Inline.parseQuotedScalar = function(scalar, context) {
+    var i, match, output;
+    i = context.i;
+    if (!(match = this.PATTERN_QUOTED_SCALAR.exec(scalar.slice(i)))) {
+      throw new ParseException('Malformed inline YAML string (' + scalar.slice(i) + ').');
+    }
+    output = match[0].substr(1, match[0].length - 2);
+    if ('"' === scalar.charAt(i)) {
+      output = Unescaper.unescapeDoubleQuotedString(output);
+    } else {
+      output = Unescaper.unescapeSingleQuotedString(output);
+    }
+    i += match[0].length;
+    context.i = i;
+    return output;
+  };
+
+  Inline.parseSequence = function(sequence, context) {
+    var e, error, i, isQuoted, len, output, ref, value;
+    output = [];
+    len = sequence.length;
+    i = context.i;
+    i += 1;
+    while (i < len) {
+      context.i = i;
+      switch (sequence.charAt(i)) {
+        case '[':
+          output.push(this.parseSequence(sequence, context));
+          i = context.i;
+          break;
+        case '{':
+          output.push(this.parseMapping(sequence, context));
+          i = context.i;
+          break;
+        case ']':
+          return output;
+        case ',':
+        case ' ':
+        case "\n":
+          break;
+        default:
+          isQuoted = ((ref = sequence.charAt(i)) === '"' || ref === "'");
+          value = this.parseScalar(sequence, [',', ']'], ['"', "'"], context);
+          i = context.i;
+          if (!isQuoted && typeof value === 'string' && (value.indexOf(': ') !== -1 || value.indexOf(":\n") !== -1)) {
+            try {
+              value = this.parseMapping('{' + value + '}');
+            } catch (error) {
+              e = error;
+            }
+          }
+          output.push(value);
+          --i;
+      }
+      ++i;
+    }
+    throw new ParseException('Malformed inline YAML string ' + sequence);
+  };
+
+  Inline.parseMapping = function(mapping, context) {
+    var done, i, key, len, output, shouldContinueWhileLoop, value;
+    output = {};
+    len = mapping.length;
+    i = context.i;
+    i += 1;
+    shouldContinueWhileLoop = false;
+    while (i < len) {
+      context.i = i;
+      switch (mapping.charAt(i)) {
+        case ' ':
+        case ',':
+        case "\n":
+          ++i;
+          context.i = i;
+          shouldContinueWhileLoop = true;
+          break;
+        case '}':
+          return output;
+      }
+      if (shouldContinueWhileLoop) {
+        shouldContinueWhileLoop = false;
+        continue;
+      }
+      key = this.parseScalar(mapping, [':', ' ', "\n"], ['"', "'"], context, false);
+      i = context.i;
+      done = false;
+      while (i < len) {
+        context.i = i;
+        switch (mapping.charAt(i)) {
+          case '[':
+            value = this.parseSequence(mapping, context);
+            i = context.i;
+            if (output[key] === void 0) {
+              output[key] = value;
+            }
+            done = true;
+            break;
+          case '{':
+            value = this.parseMapping(mapping, context);
+            i = context.i;
+            if (output[key] === void 0) {
+              output[key] = value;
+            }
+            done = true;
+            break;
+          case ':':
+          case ' ':
+          case "\n":
+            break;
+          default:
+            value = this.parseScalar(mapping, [',', '}'], ['"', "'"], context);
+            i = context.i;
+            if (output[key] === void 0) {
+              output[key] = value;
+            }
+            done = true;
+            --i;
+        }
+        ++i;
+        if (done) {
+          break;
+        }
+      }
+    }
+    throw new ParseException('Malformed inline YAML string ' + mapping);
+  };
+
+  Inline.evaluateScalar = function(scalar, context) {
+    var cast, date, exceptionOnInvalidType, firstChar, firstSpace, firstWord, objectDecoder, raw, scalarLower, subValue, trimmedScalar;
+    scalar = Utils.trim(scalar);
+    scalarLower = scalar.toLowerCase();
+    switch (scalarLower) {
+      case 'null':
+      case '':
+      case '~':
+        return null;
+      case 'true':
+        return true;
+      case 'false':
+        return false;
+      case '.inf':
+        return Infinity;
+      case '.nan':
+        return NaN;
+      case '-.inf':
+        return Infinity;
+      default:
+        firstChar = scalarLower.charAt(0);
+        switch (firstChar) {
+          case '!':
+            firstSpace = scalar.indexOf(' ');
+            if (firstSpace === -1) {
+              firstWord = scalarLower;
+            } else {
+              firstWord = scalarLower.slice(0, firstSpace);
+            }
+            switch (firstWord) {
+              case '!':
+                if (firstSpace !== -1) {
+                  return parseInt(this.parseScalar(scalar.slice(2)));
+                }
+                return null;
+              case '!str':
+                return Utils.ltrim(scalar.slice(4));
+              case '!!str':
+                return Utils.ltrim(scalar.slice(5));
+              case '!!int':
+                return parseInt(this.parseScalar(scalar.slice(5)));
+              case '!!bool':
+                return Utils.parseBoolean(this.parseScalar(scalar.slice(6)), false);
+              case '!!float':
+                return parseFloat(this.parseScalar(scalar.slice(7)));
+              case '!!timestamp':
+                return Utils.stringToDate(Utils.ltrim(scalar.slice(11)));
+              default:
+                if (context == null) {
+                  context = {
+                    exceptionOnInvalidType: this.settings.exceptionOnInvalidType,
+                    objectDecoder: this.settings.objectDecoder,
+                    i: 0
+                  };
+                }
+                objectDecoder = context.objectDecoder, exceptionOnInvalidType = context.exceptionOnInvalidType;
+                if (objectDecoder) {
+                  trimmedScalar = Utils.rtrim(scalar);
+                  firstSpace = trimmedScalar.indexOf(' ');
+                  if (firstSpace === -1) {
+                    return objectDecoder(trimmedScalar, null);
+                  } else {
+                    subValue = Utils.ltrim(trimmedScalar.slice(firstSpace + 1));
+                    if (!(subValue.length > 0)) {
+                      subValue = null;
+                    }
+                    return objectDecoder(trimmedScalar.slice(0, firstSpace), subValue);
+                  }
+                }
+                if (exceptionOnInvalidType) {
+                  throw new ParseException('Custom object support when parsing a YAML file has been disabled.');
+                }
+                return null;
+            }
+            break;
+          case '0':
+            if ('0x' === scalar.slice(0, 2)) {
+              return Utils.hexDec(scalar);
+            } else if (Utils.isDigits(scalar)) {
+              return Utils.octDec(scalar);
+            } else if (Utils.isNumeric(scalar)) {
+              return parseFloat(scalar);
+            } else {
+              return scalar;
+            }
+            break;
+          case '+':
+            if (Utils.isDigits(scalar)) {
+              raw = scalar;
+              cast = parseInt(raw);
+              if (raw === String(cast)) {
+                return cast;
+              } else {
+                return raw;
+              }
+            } else if (Utils.isNumeric(scalar)) {
+              return parseFloat(scalar);
+            } else if (this.PATTERN_THOUSAND_NUMERIC_SCALAR.test(scalar)) {
+              return parseFloat(scalar.replace(',', ''));
+            }
+            return scalar;
+          case '-':
+            if (Utils.isDigits(scalar.slice(1))) {
+              if ('0' === scalar.charAt(1)) {
+                return -Utils.octDec(scalar.slice(1));
+              } else {
+                raw = scalar.slice(1);
+                cast = parseInt(raw);
+                if (raw === String(cast)) {
+                  return -cast;
+                } else {
+                  return -raw;
+                }
+              }
+            } else if (Utils.isNumeric(scalar)) {
+              return parseFloat(scalar);
+            } else if (this.PATTERN_THOUSAND_NUMERIC_SCALAR.test(scalar)) {
+              return parseFloat(scalar.replace(',', ''));
+            }
+            return scalar;
+          default:
+            if (date = Utils.stringToDate(scalar)) {
+              return date;
+            } else if (Utils.isNumeric(scalar)) {
+              return parseFloat(scalar);
+            } else if (this.PATTERN_THOUSAND_NUMERIC_SCALAR.test(scalar)) {
+              return parseFloat(scalar.replace(',', ''));
+            }
+            return scalar;
+        }
+    }
+  };
+
+  return Inline;
+
+})();
+
+module.exports = Inline;
+
+
+},{"./Escaper":2,"./Exception/DumpException":3,"./Exception/ParseException":4,"./Pattern":7,"./Unescaper":8,"./Utils":9}],6:[function(require,module,exports){
+var Inline, ParseException, Parser, Pattern, Utils;
+
+Inline = require('./Inline');
+
+Pattern = require('./Pattern');
+
+Utils = require('./Utils');
+
+ParseException = require('./Exception/ParseException');
+
+Parser = (function() {
+  Parser.prototype.PATTERN_FOLDED_SCALAR_ALL = new Pattern('^(?:(?<type>![^\\|>]*)\\s+)?(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$');
+
+  Parser.prototype.PATTERN_FOLDED_SCALAR_END = new Pattern('(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$');
+
+  Parser.prototype.PATTERN_SEQUENCE_ITEM = new Pattern('^\\-((?<leadspaces>\\s+)(?<value>.+?))?\\s*$');
+
+  Parser.prototype.PATTERN_ANCHOR_VALUE = new Pattern('^&(?<ref>[^ ]+) *(?<value>.*)');
+
+  Parser.prototype.PATTERN_COMPACT_NOTATION = new Pattern('^(?<key>' + Inline.REGEX_QUOTED_STRING + '|[^ \'"\\{\\[].*?) *\\:(\\s+(?<value>.+?))?\\s*$');
+
+  Parser.prototype.PATTERN_MAPPING_ITEM = new Pattern('^(?<key>' + Inline.REGEX_QUOTED_STRING + '|[^ \'"\\[\\{].*?) *\\:(\\s+(?<value>.+?))?\\s*$');
+
+  Parser.prototype.PATTERN_DECIMAL = new Pattern('\\d+');
+
+  Parser.prototype.PATTERN_INDENT_SPACES = new Pattern('^ +');
+
+  Parser.prototype.PATTERN_TRAILING_LINES = new Pattern('(\n*)$');
+
+  Parser.prototype.PATTERN_YAML_HEADER = new Pattern('^\\%YAML[: ][\\d\\.]+.*\n');
+
+  Parser.prototype.PATTERN_LEADING_COMMENTS = new Pattern('^(\\#.*?\n)+');
+
+  Parser.prototype.PATTERN_DOCUMENT_MARKER_START = new Pattern('^\\-\\-\\-.*?\n');
+
+  Parser.prototype.PATTERN_DOCUMENT_MARKER_END = new Pattern('^\\.\\.\\.\\s*$');
+
+  Parser.prototype.PATTERN_FOLDED_SCALAR_BY_INDENTATION = {};
+
+  Parser.prototype.CONTEXT_NONE = 0;
+
+  Parser.prototype.CONTEXT_SEQUENCE = 1;
+
+  Parser.prototype.CONTEXT_MAPPING = 2;
+
+  function Parser(offset) {
+    this.offset = offset != null ? offset : 0;
+    this.lines = [];
+    this.currentLineNb = -1;
+    this.currentLine = '';
+    this.refs = {};
+  }
+
+  Parser.prototype.parse = function(value, exceptionOnInvalidType, objectDecoder) {
+    var alias, allowOverwrite, block, c, context, data, e, error, error1, error2, first, i, indent, isRef, j, k, key, l, lastKey, len, len1, len2, len3, lineCount, m, matches, mergeNode, n, name, parsed, parsedItem, parser, ref, ref1, ref2, refName, refValue, val, values;
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectDecoder == null) {
+      objectDecoder = null;
+    }
+    this.currentLineNb = -1;
+    this.currentLine = '';
+    this.lines = this.cleanup(value).split("\n");
+    data = null;
+    context = this.CONTEXT_NONE;
+    allowOverwrite = false;
+    while (this.moveToNextLine()) {
+      if (this.isCurrentLineEmpty()) {
+        continue;
+      }
+      if ("\t" === this.currentLine[0]) {
+        throw new ParseException('A YAML file cannot contain tabs as indentation.', this.getRealCurrentLineNb() + 1, this.currentLine);
+      }
+      isRef = mergeNode = false;
+      if (values = this.PATTERN_SEQUENCE_ITEM.exec(this.currentLine)) {
+        if (this.CONTEXT_MAPPING === context) {
+          throw new ParseException('You cannot define a sequence item when in a mapping');
+        }
+        context = this.CONTEXT_SEQUENCE;
+        if (data == null) {
+          data = [];
+        }
+        if ((values.value != null) && (matches = this.PATTERN_ANCHOR_VALUE.exec(values.value))) {
+          isRef = matches.ref;
+          values.value = matches.value;
+        }
+        if (!(values.value != null) || '' === Utils.trim(values.value, ' ') || Utils.ltrim(values.value, ' ').indexOf('#') === 0) {
+          if (this.currentLineNb < this.lines.length - 1 && !this.isNextLineUnIndentedCollection()) {
+            c = this.getRealCurrentLineNb() + 1;
+            parser = new Parser(c);
+            parser.refs = this.refs;
+            data.push(parser.parse(this.getNextEmbedBlock(null, true), exceptionOnInvalidType, objectDecoder));
+          } else {
+            data.push(null);
+          }
+        } else {
+          if (((ref = values.leadspaces) != null ? ref.length : void 0) && (matches = this.PATTERN_COMPACT_NOTATION.exec(values.value))) {
+            c = this.getRealCurrentLineNb();
+            parser = new Parser(c);
+            parser.refs = this.refs;
+            block = values.value;
+            indent = this.getCurrentLineIndentation();
+            if (this.isNextLineIndented(false)) {
+              block += "\n" + this.getNextEmbedBlock(indent + values.leadspaces.length + 1, true);
+            }
+            data.push(parser.parse(block, exceptionOnInvalidType, objectDecoder));
+          } else {
+            data.push(this.parseValue(values.value, exceptionOnInvalidType, objectDecoder));
+          }
+        }
+      } else if ((values = this.PATTERN_MAPPING_ITEM.exec(this.currentLine)) && values.key.indexOf(' #') === -1) {
+        if (this.CONTEXT_SEQUENCE === context) {
+          throw new ParseException('You cannot define a mapping item when in a sequence');
+        }
+        context = this.CONTEXT_MAPPING;
+        if (data == null) {
+          data = {};
+        }
+        Inline.configure(exceptionOnInvalidType, objectDecoder);
+        try {
+          key = Inline.parseScalar(values.key);
+        } catch (error) {
+          e = error;
+          e.parsedLine = this.getRealCurrentLineNb() + 1;
+          e.snippet = this.currentLine;
+          throw e;
+        }
+        if ('<<' === key) {
+          mergeNode = true;
+          allowOverwrite = true;
+          if (((ref1 = values.value) != null ? ref1.indexOf('*') : void 0) === 0) {
+            refName = values.value.slice(1);
+            if (this.refs[refName] == null) {
+              throw new ParseException('Reference "' + refName + '" does not exist.', this.getRealCurrentLineNb() + 1, this.currentLine);
+            }
+            refValue = this.refs[refName];
+            if (typeof refValue !== 'object') {
+              throw new ParseException('YAML merge keys used with a scalar value instead of an object.', this.getRealCurrentLineNb() + 1, this.currentLine);
+            }
+            if (refValue instanceof Array) {
+              for (i = j = 0, len = refValue.length; j < len; i = ++j) {
+                value = refValue[i];
+                if (data[name = String(i)] == null) {
+                  data[name] = value;
+                }
+              }
+            } else {
+              for (key in refValue) {
+                value = refValue[key];
+                if (data[key] == null) {
+                  data[key] = value;
+                }
+              }
+            }
+          } else {
+            if ((values.value != null) && values.value !== '') {
+              value = values.value;
+            } else {
+              value = this.getNextEmbedBlock();
+            }
+            c = this.getRealCurrentLineNb() + 1;
+            parser = new Parser(c);
+            parser.refs = this.refs;
+            parsed = parser.parse(value, exceptionOnInvalidType);
+            if (typeof parsed !== 'object') {
+              throw new ParseException('YAML merge keys used with a scalar value instead of an object.', this.getRealCurrentLineNb() + 1, this.currentLine);
+            }
+            if (parsed instanceof Array) {
+              for (l = 0, len1 = parsed.length; l < len1; l++) {
+                parsedItem = parsed[l];
+                if (typeof parsedItem !== 'object') {
+                  throw new ParseException('Merge items must be objects.', this.getRealCurrentLineNb() + 1, parsedItem);
+                }
+                if (parsedItem instanceof Array) {
+                  for (i = m = 0, len2 = parsedItem.length; m < len2; i = ++m) {
+                    value = parsedItem[i];
+                    k = String(i);
+                    if (!data.hasOwnProperty(k)) {
+                      data[k] = value;
+                    }
+                  }
+                } else {
+                  for (key in parsedItem) {
+                    value = parsedItem[key];
+                    if (!data.hasOwnProperty(key)) {
+                      data[key] = value;
+                    }
+                  }
+                }
+              }
+            } else {
+              for (key in parsed) {
+                value = parsed[key];
+                if (!data.hasOwnProperty(key)) {
+                  data[key] = value;
+                }
+              }
+            }
+          }
+        } else if ((values.value != null) && (matches = this.PATTERN_ANCHOR_VALUE.exec(values.value))) {
+          isRef = matches.ref;
+          values.value = matches.value;
+        }
+        if (mergeNode) {
+
+        } else if (!(values.value != null) || '' === Utils.trim(values.value, ' ') || Utils.ltrim(values.value, ' ').indexOf('#') === 0) {
+          if (!(this.isNextLineIndented()) && !(this.isNextLineUnIndentedCollection())) {
+            if (allowOverwrite || data[key] === void 0) {
+              data[key] = null;
+            }
+          } else {
+            c = this.getRealCurrentLineNb() + 1;
+            parser = new Parser(c);
+            parser.refs = this.refs;
+            val = parser.parse(this.getNextEmbedBlock(), exceptionOnInvalidType, objectDecoder);
+            if (allowOverwrite || data[key] === void 0) {
+              data[key] = val;
+            }
+          }
+        } else {
+          val = this.parseValue(values.value, exceptionOnInvalidType, objectDecoder);
+          if (allowOverwrite || data[key] === void 0) {
+            data[key] = val;
+          }
+        }
+      } else {
+        lineCount = this.lines.length;
+        if (1 === lineCount || (2 === lineCount && Utils.isEmpty(this.lines[1]))) {
+          try {
+            value = Inline.parse(this.lines[0], exceptionOnInvalidType, objectDecoder);
+          } catch (error1) {
+            e = error1;
+            e.parsedLine = this.getRealCurrentLineNb() + 1;
+            e.snippet = this.currentLine;
+            throw e;
+          }
+          if (typeof value === 'object') {
+            if (value instanceof Array) {
+              first = value[0];
+            } else {
+              for (key in value) {
+                first = value[key];
+                break;
+              }
+            }
+            if (typeof first === 'string' && first.indexOf('*') === 0) {
+              data = [];
+              for (n = 0, len3 = value.length; n < len3; n++) {
+                alias = value[n];
+                data.push(this.refs[alias.slice(1)]);
+              }
+              value = data;
+            }
+          }
+          return value;
+        } else if ((ref2 = Utils.ltrim(value).charAt(0)) === '[' || ref2 === '{') {
+          try {
+            return Inline.parse(value, exceptionOnInvalidType, objectDecoder);
+          } catch (error2) {
+            e = error2;
+            e.parsedLine = this.getRealCurrentLineNb() + 1;
+            e.snippet = this.currentLine;
+            throw e;
+          }
+        }
+        throw new ParseException('Unable to parse.', this.getRealCurrentLineNb() + 1, this.currentLine);
+      }
+      if (isRef) {
+        if (data instanceof Array) {
+          this.refs[isRef] = data[data.length - 1];
+        } else {
+          lastKey = null;
+          for (key in data) {
+            lastKey = key;
+          }
+          this.refs[isRef] = data[lastKey];
+        }
+      }
+    }
+    if (Utils.isEmpty(data)) {
+      return null;
+    } else {
+      return data;
+    }
+  };
+
+  Parser.prototype.getRealCurrentLineNb = function() {
+    return this.currentLineNb + this.offset;
+  };
+
+  Parser.prototype.getCurrentLineIndentation = function() {
+    return this.currentLine.length - Utils.ltrim(this.currentLine, ' ').length;
+  };
+
+  Parser.prototype.getNextEmbedBlock = function(indentation, includeUnindentedCollection) {
+    var data, indent, isItUnindentedCollection, newIndent, removeComments, removeCommentsPattern, unindentedEmbedBlock;
+    if (indentation == null) {
+      indentation = null;
+    }
+    if (includeUnindentedCollection == null) {
+      includeUnindentedCollection = false;
+    }
+    this.moveToNextLine();
+    if (indentation == null) {
+      newIndent = this.getCurrentLineIndentation();
+      unindentedEmbedBlock = this.isStringUnIndentedCollectionItem(this.currentLine);
+      if (!(this.isCurrentLineEmpty()) && 0 === newIndent && !unindentedEmbedBlock) {
+        throw new ParseException('Indentation problem.', this.getRealCurrentLineNb() + 1, this.currentLine);
+      }
+    } else {
+      newIndent = indentation;
+    }
+    data = [this.currentLine.slice(newIndent)];
+    if (!includeUnindentedCollection) {
+      isItUnindentedCollection = this.isStringUnIndentedCollectionItem(this.currentLine);
+    }
+    removeCommentsPattern = this.PATTERN_FOLDED_SCALAR_END;
+    removeComments = !removeCommentsPattern.test(this.currentLine);
+    while (this.moveToNextLine()) {
+      indent = this.getCurrentLineIndentation();
+      if (indent === newIndent) {
+        removeComments = !removeCommentsPattern.test(this.currentLine);
+      }
+      if (isItUnindentedCollection && !this.isStringUnIndentedCollectionItem(this.currentLine) && indent === newIndent) {
+        this.moveToPreviousLine();
+        break;
+      }
+      if (this.isCurrentLineBlank()) {
+        data.push(this.currentLine.slice(newIndent));
+        continue;
+      }
+      if (removeComments && this.isCurrentLineComment()) {
+        if (indent === newIndent) {
+          continue;
+        }
+      }
+      if (indent >= newIndent) {
+        data.push(this.currentLine.slice(newIndent));
+      } else if (Utils.ltrim(this.currentLine).charAt(0) === '#') {
+
+      } else if (0 === indent) {
+        this.moveToPreviousLine();
+        break;
+      } else {
+        throw new ParseException('Indentation problem.', this.getRealCurrentLineNb() + 1, this.currentLine);
+      }
+    }
+    return data.join("\n");
+  };
+
+  Parser.prototype.moveToNextLine = function() {
+    if (this.currentLineNb >= this.lines.length - 1) {
+      return false;
+    }
+    this.currentLine = this.lines[++this.currentLineNb];
+    return true;
+  };
+
+  Parser.prototype.moveToPreviousLine = function() {
+    this.currentLine = this.lines[--this.currentLineNb];
+  };
+
+  Parser.prototype.parseValue = function(value, exceptionOnInvalidType, objectDecoder) {
+    var e, error, error1, foldedIndent, matches, modifiers, pos, ref, ref1, val;
+    if (0 === value.indexOf('*')) {
+      pos = value.indexOf('#');
+      if (pos !== -1) {
+        value = value.substr(1, pos - 2);
+      } else {
+        value = value.slice(1);
+      }
+      if (this.refs[value] === void 0) {
+        throw new ParseException('Reference "' + value + '" does not exist.', this.currentLine);
+      }
+      return this.refs[value];
+    }
+    if (matches = this.PATTERN_FOLDED_SCALAR_ALL.exec(value)) {
+      modifiers = (ref = matches.modifiers) != null ? ref : '';
+      foldedIndent = Math.abs(parseInt(modifiers));
+      if (isNaN(foldedIndent)) {
+        foldedIndent = 0;
+      }
+      val = this.parseFoldedScalar(matches.separator, this.PATTERN_DECIMAL.replace(modifiers, ''), foldedIndent);
+      if (matches.type != null) {
+        Inline.configure(exceptionOnInvalidType, objectDecoder);
+        return Inline.parseScalar(matches.type + ' ' + val);
+      } else {
+        return val;
+      }
+    }
+    try {
+      return Inline.parse(value, exceptionOnInvalidType, objectDecoder);
+    } catch (error) {
+      e = error;
+      if (((ref1 = value.charAt(0)) === '[' || ref1 === '{') && e instanceof ParseException && this.isNextLineIndented()) {
+        value += "\n" + this.getNextEmbedBlock();
+        try {
+          return Inline.parse(value, exceptionOnInvalidType, objectDecoder);
+        } catch (error1) {
+          e = error1;
+          e.parsedLine = this.getRealCurrentLineNb() + 1;
+          e.snippet = this.currentLine;
+          throw e;
+        }
+      } else {
+        e.parsedLine = this.getRealCurrentLineNb() + 1;
+        e.snippet = this.currentLine;
+        throw e;
+      }
+    }
+  };
+
+  Parser.prototype.parseFoldedScalar = function(separator, indicator, indentation) {
+    var isCurrentLineBlank, j, len, line, matches, newText, notEOF, pattern, ref, text;
+    if (indicator == null) {
+      indicator = '';
+    }
+    if (indentation == null) {
+      indentation = 0;
+    }
+    notEOF = this.moveToNextLine();
+    if (!notEOF) {
+      return '';
+    }
+    isCurrentLineBlank = this.isCurrentLineBlank();
+    text = '';
+    while (notEOF && isCurrentLineBlank) {
+      if (notEOF = this.moveToNextLine()) {
+        text += "\n";
+        isCurrentLineBlank = this.isCurrentLineBlank();
+      }
+    }
+    if (0 === indentation) {
+      if (matches = this.PATTERN_INDENT_SPACES.exec(this.currentLine)) {
+        indentation = matches[0].length;
+      }
+    }
+    if (indentation > 0) {
+      pattern = this.PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation];
+      if (pattern == null) {
+        pattern = new Pattern('^ {' + indentation + '}(.*)$');
+        Parser.prototype.PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation] = pattern;
+      }
+      while (notEOF && (isCurrentLineBlank || (matches = pattern.exec(this.currentLine)))) {
+        if (isCurrentLineBlank) {
+          text += this.currentLine.slice(indentation);
+        } else {
+          text += matches[1];
+        }
+        if (notEOF = this.moveToNextLine()) {
+          text += "\n";
+          isCurrentLineBlank = this.isCurrentLineBlank();
+        }
+      }
+    } else if (notEOF) {
+      text += "\n";
+    }
+    if (notEOF) {
+      this.moveToPreviousLine();
+    }
+    if ('>' === separator) {
+      newText = '';
+      ref = text.split("\n");
+      for (j = 0, len = ref.length; j < len; j++) {
+        line = ref[j];
+        if (line.length === 0 || line.charAt(0) === ' ') {
+          newText = Utils.rtrim(newText, ' ') + line + "\n";
+        } else {
+          newText += line + ' ';
+        }
+      }
+      text = newText;
+    }
+    if ('+' !== indicator) {
+      text = Utils.rtrim(text);
+    }
+    if ('' === indicator) {
+      text = this.PATTERN_TRAILING_LINES.replace(text, "\n");
+    } else if ('-' === indicator) {
+      text = this.PATTERN_TRAILING_LINES.replace(text, '');
+    }
+    return text;
+  };
+
+  Parser.prototype.isNextLineIndented = function(ignoreComments) {
+    var EOF, currentIndentation, ret;
+    if (ignoreComments == null) {
+      ignoreComments = true;
+    }
+    currentIndentation = this.getCurrentLineIndentation();
+    EOF = !this.moveToNextLine();
+    if (ignoreComments) {
+      while (!EOF && this.isCurrentLineEmpty()) {
+        EOF = !this.moveToNextLine();
+      }
+    } else {
+      while (!EOF && this.isCurrentLineBlank()) {
+        EOF = !this.moveToNextLine();
+      }
+    }
+    if (EOF) {
+      return false;
+    }
+    ret = false;
+    if (this.getCurrentLineIndentation() > currentIndentation) {
+      ret = true;
+    }
+    this.moveToPreviousLine();
+    return ret;
+  };
+
+  Parser.prototype.isCurrentLineEmpty = function() {
+    var trimmedLine;
+    trimmedLine = Utils.trim(this.currentLine, ' ');
+    return trimmedLine.length === 0 || trimmedLine.charAt(0) === '#';
+  };
+
+  Parser.prototype.isCurrentLineBlank = function() {
+    return '' === Utils.trim(this.currentLine, ' ');
+  };
+
+  Parser.prototype.isCurrentLineComment = function() {
+    var ltrimmedLine;
+    ltrimmedLine = Utils.ltrim(this.currentLine, ' ');
+    return ltrimmedLine.charAt(0) === '#';
+  };
+
+  Parser.prototype.cleanup = function(value) {
+    var count, i, indent, j, l, len, len1, line, lines, ref, ref1, ref2, smallestIndent, trimmedValue;
+    if (value.indexOf("\r") !== -1) {
+      value = value.split("\r\n").join("\n").split("\r").join("\n");
+    }
+    count = 0;
+    ref = this.PATTERN_YAML_HEADER.replaceAll(value, ''), value = ref[0], count = ref[1];
+    this.offset += count;
+    ref1 = this.PATTERN_LEADING_COMMENTS.replaceAll(value, '', 1), trimmedValue = ref1[0], count = ref1[1];
+    if (count === 1) {
+      this.offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n");
+      value = trimmedValue;
+    }
+    ref2 = this.PATTERN_DOCUMENT_MARKER_START.replaceAll(value, '', 1), trimmedValue = ref2[0], count = ref2[1];
+    if (count === 1) {
+      this.offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n");
+      value = trimmedValue;
+      value = this.PATTERN_DOCUMENT_MARKER_END.replace(value, '');
+    }
+    lines = value.split("\n");
+    smallestIndent = -1;
+    for (j = 0, len = lines.length; j < len; j++) {
+      line = lines[j];
+      if (Utils.trim(line, ' ').length === 0) {
+        continue;
+      }
+      indent = line.length - Utils.ltrim(line).length;
+      if (smallestIndent === -1 || indent < smallestIndent) {
+        smallestIndent = indent;
+      }
+    }
+    if (smallestIndent > 0) {
+      for (i = l = 0, len1 = lines.length; l < len1; i = ++l) {
+        line = lines[i];
+        lines[i] = line.slice(smallestIndent);
+      }
+      value = lines.join("\n");
+    }
+    return value;
+  };
+
+  Parser.prototype.isNextLineUnIndentedCollection = function(currentIndentation) {
+    var notEOF, ret;
+    if (currentIndentation == null) {
+      currentIndentation = null;
+    }
+    if (currentIndentation == null) {
+      currentIndentation = this.getCurrentLineIndentation();
+    }
+    notEOF = this.moveToNextLine();
+    while (notEOF && this.isCurrentLineEmpty()) {
+      notEOF = this.moveToNextLine();
+    }
+    if (false === notEOF) {
+      return false;
+    }
+    ret = false;
+    if (this.getCurrentLineIndentation() === currentIndentation && this.isStringUnIndentedCollectionItem(this.currentLine)) {
+      ret = true;
+    }
+    this.moveToPreviousLine();
+    return ret;
+  };
+
+  Parser.prototype.isStringUnIndentedCollectionItem = function() {
+    return this.currentLine === '-' || this.currentLine.slice(0, 2) === '- ';
+  };
+
+  return Parser;
+
+})();
+
+module.exports = Parser;
+
+
+},{"./Exception/ParseException":4,"./Inline":5,"./Pattern":7,"./Utils":9}],7:[function(require,module,exports){
+var Pattern;
+
+Pattern = (function() {
+  Pattern.prototype.regex = null;
+
+  Pattern.prototype.rawRegex = null;
+
+  Pattern.prototype.cleanedRegex = null;
+
+  Pattern.prototype.mapping = null;
+
+  function Pattern(rawRegex, modifiers) {
+    var _char, capturingBracketNumber, cleanedRegex, i, len, mapping, name, part, subChar;
+    if (modifiers == null) {
+      modifiers = '';
+    }
+    cleanedRegex = '';
+    len = rawRegex.length;
+    mapping = null;
+    capturingBracketNumber = 0;
+    i = 0;
+    while (i < len) {
+      _char = rawRegex.charAt(i);
+      if (_char === '\\') {
+        cleanedRegex += rawRegex.slice(i, +(i + 1) + 1 || 9e9);
+        i++;
+      } else if (_char === '(') {
+        if (i < len - 2) {
+          part = rawRegex.slice(i, +(i + 2) + 1 || 9e9);
+          if (part === '(?:') {
+            i += 2;
+            cleanedRegex += part;
+          } else if (part === '(?<') {
+            capturingBracketNumber++;
+            i += 2;
+            name = '';
+            while (i + 1 < len) {
+              subChar = rawRegex.charAt(i + 1);
+              if (subChar === '>') {
+                cleanedRegex += '(';
+                i++;
+                if (name.length > 0) {
+                  if (mapping == null) {
+                    mapping = {};
+                  }
+                  mapping[name] = capturingBracketNumber;
+                }
+                break;
+              } else {
+                name += subChar;
+              }
+              i++;
+            }
+          } else {
+            cleanedRegex += _char;
+            capturingBracketNumber++;
+          }
+        } else {
+          cleanedRegex += _char;
+        }
+      } else {
+        cleanedRegex += _char;
+      }
+      i++;
+    }
+    this.rawRegex = rawRegex;
+    this.cleanedRegex = cleanedRegex;
+    this.regex = new RegExp(this.cleanedRegex, 'g' + modifiers.replace('g', ''));
+    this.mapping = mapping;
+  }
+
+  Pattern.prototype.exec = function(str) {
+    var index, matches, name, ref;
+    this.regex.lastIndex = 0;
+    matches = this.regex.exec(str);
+    if (matches == null) {
+      return null;
+    }
+    if (this.mapping != null) {
+      ref = this.mapping;
+      for (name in ref) {
+        index = ref[name];
+        matches[name] = matches[index];
+      }
+    }
+    return matches;
+  };
+
+  Pattern.prototype.test = function(str) {
+    this.regex.lastIndex = 0;
+    return this.regex.test(str);
+  };
+
+  Pattern.prototype.replace = function(str, replacement) {
+    this.regex.lastIndex = 0;
+    return str.replace(this.regex, replacement);
+  };
+
+  Pattern.prototype.replaceAll = function(str, replacement, limit) {
+    var count;
+    if (limit == null) {
+      limit = 0;
+    }
+    this.regex.lastIndex = 0;
+    count = 0;
+    while (this.regex.test(str) && (limit === 0 || count < limit)) {
+      this.regex.lastIndex = 0;
+      str = str.replace(this.regex, '');
+      count++;
+    }
+    return [str, count];
+  };
+
+  return Pattern;
+
+})();
+
+module.exports = Pattern;
+
+
+},{}],8:[function(require,module,exports){
+var Pattern, Unescaper, Utils;
+
+Utils = require('./Utils');
+
+Pattern = require('./Pattern');
+
+Unescaper = (function() {
+  function Unescaper() {}
+
+  Unescaper.PATTERN_ESCAPED_CHARACTER = new Pattern('\\\\([0abt\tnvfre "\\/\\\\N_LP]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})');
+
+  Unescaper.unescapeSingleQuotedString = function(value) {
+    return value.replace(/\'\'/g, '\'');
+  };
+
+  Unescaper.unescapeDoubleQuotedString = function(value) {
+    if (this._unescapeCallback == null) {
+      this._unescapeCallback = (function(_this) {
+        return function(str) {
+          return _this.unescapeCharacter(str);
+        };
+      })(this);
+    }
+    return this.PATTERN_ESCAPED_CHARACTER.replace(value, this._unescapeCallback);
+  };
+
+  Unescaper.unescapeCharacter = function(value) {
+    var ch;
+    ch = String.fromCharCode;
+    switch (value.charAt(1)) {
+      case '0':
+        return ch(0);
+      case 'a':
+        return ch(7);
+      case 'b':
+        return ch(8);
+      case 't':
+        return "\t";
+      case "\t":
+        return "\t";
+      case 'n':
+        return "\n";
+      case 'v':
+        return ch(11);
+      case 'f':
+        return ch(12);
+      case 'r':
+        return ch(13);
+      case 'e':
+        return ch(27);
+      case ' ':
+        return ' ';
+      case '"':
+        return '"';
+      case '/':
+        return '/';
+      case '\\':
+        return '\\';
+      case 'N':
+        return ch(0x0085);
+      case '_':
+        return ch(0x00A0);
+      case 'L':
+        return ch(0x2028);
+      case 'P':
+        return ch(0x2029);
+      case 'x':
+        return Utils.utf8chr(Utils.hexDec(value.substr(2, 2)));
+      case 'u':
+        return Utils.utf8chr(Utils.hexDec(value.substr(2, 4)));
+      case 'U':
+        return Utils.utf8chr(Utils.hexDec(value.substr(2, 8)));
+      default:
+        return '';
+    }
+  };
+
+  return Unescaper;
+
+})();
+
+module.exports = Unescaper;
+
+
+},{"./Pattern":7,"./Utils":9}],9:[function(require,module,exports){
+var Pattern, Utils;
+
+Pattern = require('./Pattern');
+
+Utils = (function() {
+  function Utils() {}
+
+  Utils.REGEX_LEFT_TRIM_BY_CHAR = {};
+
+  Utils.REGEX_RIGHT_TRIM_BY_CHAR = {};
+
+  Utils.REGEX_SPACES = /\s+/g;
+
+  Utils.REGEX_DIGITS = /^\d+$/;
+
+  Utils.REGEX_OCTAL = /[^0-7]/gi;
+
+  Utils.REGEX_HEXADECIMAL = /[^a-f0-9]/gi;
+
+  Utils.PATTERN_DATE = new Pattern('^' + '(?<year>[0-9][0-9][0-9][0-9])' + '-(?<month>[0-9][0-9]?)' + '-(?<day>[0-9][0-9]?)' + '(?:(?:[Tt]|[ \t]+)' + '(?<hour>[0-9][0-9]?)' + ':(?<minute>[0-9][0-9])' + ':(?<second>[0-9][0-9])' + '(?:\.(?<fraction>[0-9]*))?' + '(?:[ \t]*(?<tz>Z|(?<tz_sign>[-+])(?<tz_hour>[0-9][0-9]?)' + '(?::(?<tz_minute>[0-9][0-9]))?))?)?' + '$', 'i');
+
+  Utils.LOCAL_TIMEZONE_OFFSET = new Date().getTimezoneOffset() * 60 * 1000;
+
+  Utils.trim = function(str, _char) {
+    var regexLeft, regexRight;
+    if (_char == null) {
+      _char = '\\s';
+    }
+    return str.trim();
+    regexLeft = this.REGEX_LEFT_TRIM_BY_CHAR[_char];
+    if (regexLeft == null) {
+      this.REGEX_LEFT_TRIM_BY_CHAR[_char] = regexLeft = new RegExp('^' + _char + '' + _char + '*');
+    }
+    regexLeft.lastIndex = 0;
+    regexRight = this.REGEX_RIGHT_TRIM_BY_CHAR[_char];
+    if (regexRight == null) {
+      this.REGEX_RIGHT_TRIM_BY_CHAR[_char] = regexRight = new RegExp(_char + '' + _char + '*$');
+    }
+    regexRight.lastIndex = 0;
+    return str.replace(regexLeft, '').replace(regexRight, '');
+  };
+
+  Utils.ltrim = function(str, _char) {
+    var regexLeft;
+    if (_char == null) {
+      _char = '\\s';
+    }
+    regexLeft = this.REGEX_LEFT_TRIM_BY_CHAR[_char];
+    if (regexLeft == null) {
+      this.REGEX_LEFT_TRIM_BY_CHAR[_char] = regexLeft = new RegExp('^' + _char + '' + _char + '*');
+    }
+    regexLeft.lastIndex = 0;
+    return str.replace(regexLeft, '');
+  };
+
+  Utils.rtrim = function(str, _char) {
+    var regexRight;
+    if (_char == null) {
+      _char = '\\s';
+    }
+    regexRight = this.REGEX_RIGHT_TRIM_BY_CHAR[_char];
+    if (regexRight == null) {
+      this.REGEX_RIGHT_TRIM_BY_CHAR[_char] = regexRight = new RegExp(_char + '' + _char + '*$');
+    }
+    regexRight.lastIndex = 0;
+    return str.replace(regexRight, '');
+  };
+
+  Utils.isEmpty = function(value) {
+    return !value || value === '' || value === '0' || (value instanceof Array && value.length === 0);
+  };
+
+  Utils.subStrCount = function(string, subString, start, length) {
+    var c, i, j, len, ref, sublen;
+    c = 0;
+    string = '' + string;
+    subString = '' + subString;
+    if (start != null) {
+      string = string.slice(start);
+    }
+    if (length != null) {
+      string = string.slice(0, length);
+    }
+    len = string.length;
+    sublen = subString.length;
+    for (i = j = 0, ref = len; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
+      if (subString === string.slice(i, sublen)) {
+        c++;
+        i += sublen - 1;
+      }
+    }
+    return c;
+  };
+
+  Utils.isDigits = function(input) {
+    this.REGEX_DIGITS.lastIndex = 0;
+    return this.REGEX_DIGITS.test(input);
+  };
+
+  Utils.octDec = function(input) {
+    this.REGEX_OCTAL.lastIndex = 0;
+    return parseInt((input + '').replace(this.REGEX_OCTAL, ''), 8);
+  };
+
+  Utils.hexDec = function(input) {
+    this.REGEX_HEXADECIMAL.lastIndex = 0;
+    input = this.trim(input);
+    if ((input + '').slice(0, 2) === '0x') {
+      input = (input + '').slice(2);
+    }
+    return parseInt((input + '').replace(this.REGEX_HEXADECIMAL, ''), 16);
+  };
+
+  Utils.utf8chr = function(c) {
+    var ch;
+    ch = String.fromCharCode;
+    if (0x80 > (c %= 0x200000)) {
+      return ch(c);
+    }
+    if (0x800 > c) {
+      return ch(0xC0 | c >> 6) + ch(0x80 | c & 0x3F);
+    }
+    if (0x10000 > c) {
+      return ch(0xE0 | c >> 12) + ch(0x80 | c >> 6 & 0x3F) + ch(0x80 | c & 0x3F);
+    }
+    return ch(0xF0 | c >> 18) + ch(0x80 | c >> 12 & 0x3F) + ch(0x80 | c >> 6 & 0x3F) + ch(0x80 | c & 0x3F);
+  };
+
+  Utils.parseBoolean = function(input, strict) {
+    var lowerInput;
+    if (strict == null) {
+      strict = true;
+    }
+    if (typeof input === 'string') {
+      lowerInput = input.toLowerCase();
+      if (!strict) {
+        if (lowerInput === 'no') {
+          return false;
+        }
+      }
+      if (lowerInput === '0') {
+        return false;
+      }
+      if (lowerInput === 'false') {
+        return false;
+      }
+      if (lowerInput === '') {
+        return false;
+      }
+      return true;
+    }
+    return !!input;
+  };
+
+  Utils.isNumeric = function(input) {
+    this.REGEX_SPACES.lastIndex = 0;
+    return typeof input === 'number' || typeof input === 'string' && !isNaN(input) && input.replace(this.REGEX_SPACES, '') !== '';
+  };
+
+  Utils.stringToDate = function(str) {
+    var date, day, fraction, hour, info, minute, month, second, tz_hour, tz_minute, tz_offset, year;
+    if (!(str != null ? str.length : void 0)) {
+      return null;
+    }
+    info = this.PATTERN_DATE.exec(str);
+    if (!info) {
+      return null;
+    }
+    year = parseInt(info.year, 10);
+    month = parseInt(info.month, 10) - 1;
+    day = parseInt(info.day, 10);
+    if (info.hour == null) {
+      date = new Date(Date.UTC(year, month, day));
+      return date;
+    }
+    hour = parseInt(info.hour, 10);
+    minute = parseInt(info.minute, 10);
+    second = parseInt(info.second, 10);
+    if (info.fraction != null) {
+      fraction = info.fraction.slice(0, 3);
+      while (fraction.length < 3) {
+        fraction += '0';
+      }
+      fraction = parseInt(fraction, 10);
+    } else {
+      fraction = 0;
+    }
+    if (info.tz != null) {
+      tz_hour = parseInt(info.tz_hour, 10);
+      if (info.tz_minute != null) {
+        tz_minute = parseInt(info.tz_minute, 10);
+      } else {
+        tz_minute = 0;
+      }
+      tz_offset = (tz_hour * 60 + tz_minute) * 60000;
+      if ('-' === info.tz_sign) {
+        tz_offset *= -1;
+      }
+    }
+    date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction));
+    if (tz_offset) {
+      date.setTime(date.getTime() + tz_offset);
+    }
+    return date;
+  };
+
+  Utils.strRepeat = function(str, number) {
+    var i, res;
+    res = '';
+    i = 0;
+    while (i < number) {
+      res += str;
+      i++;
+    }
+    return res;
+  };
+
+  Utils.getStringFromFile = function(path, callback) {
+    var data, fs, j, len1, name, ref, req, xhr;
+    if (callback == null) {
+      callback = null;
+    }
+    xhr = null;
+    if (typeof window !== "undefined" && window !== null) {
+      if (window.XMLHttpRequest) {
+        xhr = new XMLHttpRequest();
+      } else if (window.ActiveXObject) {
+        ref = ["Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.3.0", "Msxml2.XMLHTTP", "Microsoft.XMLHTTP"];
+        for (j = 0, len1 = ref.length; j < len1; j++) {
+          name = ref[j];
+          try {
+            xhr = new ActiveXObject(name);
+          } catch (undefined) {}
+        }
+      }
+    }
+    if (xhr != null) {
+      if (callback != null) {
+        xhr.onreadystatechange = function() {
+          if (xhr.readyState === 4) {
+            if (xhr.status === 200 || xhr.status === 0) {
+              return callback(xhr.responseText);
+            } else {
+              return callback(null);
+            }
+          }
+        };
+        xhr.open('GET', path, true);
+        return xhr.send(null);
+      } else {
+        xhr.open('GET', path, false);
+        xhr.send(null);
+        if (xhr.status === 200 || xhr.status === 0) {
+          return xhr.responseText;
+        }
+        return null;
+      }
+    } else {
+      req = require;
+      fs = req('fs');
+      if (callback != null) {
+        return fs.readFile(path, function(err, data) {
+          if (err) {
+            return callback(null);
+          } else {
+            return callback(String(data));
+          }
+        });
+      } else {
+        data = fs.readFileSync(path);
+        if (data != null) {
+          return String(data);
+        }
+        return null;
+      }
+    }
+  };
+
+  return Utils;
+
+})();
+
+module.exports = Utils;
+
+
+},{"./Pattern":7}],10:[function(require,module,exports){
+var Dumper, Parser, Utils, Yaml;
+
+Parser = require('./Parser');
+
+Dumper = require('./Dumper');
+
+Utils = require('./Utils');
+
+Yaml = (function() {
+  function Yaml() {}
+
+  Yaml.parse = function(input, exceptionOnInvalidType, objectDecoder) {
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectDecoder == null) {
+      objectDecoder = null;
+    }
+    return new Parser().parse(input, exceptionOnInvalidType, objectDecoder);
+  };
+
+  Yaml.parseFile = function(path, callback, exceptionOnInvalidType, objectDecoder) {
+    var input;
+    if (callback == null) {
+      callback = null;
+    }
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectDecoder == null) {
+      objectDecoder = null;
+    }
+    if (callback != null) {
+      return Utils.getStringFromFile(path, (function(_this) {
+        return function(input) {
+          var result;
+          result = null;
+          if (input != null) {
+            result = _this.parse(input, exceptionOnInvalidType, objectDecoder);
+          }
+          callback(result);
+        };
+      })(this));
+    } else {
+      input = Utils.getStringFromFile(path);
+      if (input != null) {
+        return this.parse(input, exceptionOnInvalidType, objectDecoder);
+      }
+      return null;
+    }
+  };
+
+  Yaml.dump = function(input, inline, indent, exceptionOnInvalidType, objectEncoder) {
+    var yaml;
+    if (inline == null) {
+      inline = 2;
+    }
+    if (indent == null) {
+      indent = 4;
+    }
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectEncoder == null) {
+      objectEncoder = null;
+    }
+    yaml = new Dumper();
+    yaml.indentation = indent;
+    return yaml.dump(input, inline, 0, exceptionOnInvalidType, objectEncoder);
+  };
+
+  Yaml.register = function() {
+    var require_handler;
+    require_handler = function(module, filename) {
+      return module.exports = YAML.parseFile(filename);
+    };
+    if ((typeof require !== "undefined" && require !== null ? require.extensions : void 0) != null) {
+      require.extensions['.yml'] = require_handler;
+      return require.extensions['.yaml'] = require_handler;
+    }
+  };
+
+  Yaml.stringify = function(input, inline, indent, exceptionOnInvalidType, objectEncoder) {
+    return this.dump(input, inline, indent, exceptionOnInvalidType, objectEncoder);
+  };
+
+  Yaml.load = function(path, callback, exceptionOnInvalidType, objectDecoder) {
+    return this.parseFile(path, callback, exceptionOnInvalidType, objectDecoder);
+  };
+
+  return Yaml;
+
+})();
+
+if (typeof window !== "undefined" && window !== null) {
+  window.YAML = Yaml;
+}
+
+if (typeof window === "undefined" || window === null) {
+  this.YAML = Yaml;
+}
+
+module.exports = Yaml;
+
+
+},{"./Dumper":1,"./Parser":6,"./Utils":9}]},{},[10])
+//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3Vzci9sb2NhbC9saWIvbm9kZV9tb2R1bGVzL2Jyb3dzZXJpZnkvbm9kZV9tb2R1bGVzL2Jyb3dzZXItcGFjay9fcHJlbHVkZS5qcyIsIi9Vc2Vycy9qZXJlbXlmYS9Eb2N1bWVudHMvUHJvamV0cy95YW1sLmpzL3NyYy9EdW1wZXIuY29mZmVlIiwiL1VzZXJzL2plcmVteWZhL0RvY3VtZW50cy9Qcm9qZXRzL3lhbWwuanMvc3JjL0VzY2FwZXIuY29mZmVlIiwiL1VzZXJzL2plcmVteWZhL0RvY3VtZW50cy9Qcm9qZXRzL3lhbWwuanMvc3JjL0V4Y2VwdGlvbi9EdW1wRXhjZXB0aW9uLmNvZmZlZSIsIi9Vc2Vycy9qZXJlbXlmYS9Eb2N1bWVudHMvUHJvamV0cy95YW1sLmpzL3NyYy9FeGNlcHRpb24vUGFyc2VFeGNlcHRpb24uY29mZmVlIiwiL1VzZXJzL2plcmVteWZhL0RvY3VtZW50cy9Qcm9qZXRzL3lhbWwuanMvc3JjL0lubGluZS5jb2ZmZWUiLCIvVXNlcnMvamVyZW15ZmEvRG9jdW1lbnRzL1Byb2pldHMveWFtbC5qcy9zcmMvUGFyc2VyLmNvZmZlZSIsIi9Vc2Vycy9qZXJlbXlmYS9Eb2N1bWVudHMvUHJvamV0cy95YW1sLmpzL3NyYy9QYXR0ZXJuLmNvZmZlZSIsIi9Vc2Vycy9qZXJlbXlmYS9Eb2N1bWVudHMvUHJvamV0cy95YW1sLmpzL3NyYy9VbmVzY2FwZXIuY29mZmVlIiwiL1VzZXJzL2plcmVteWZhL0RvY3VtZW50cy9Qcm9qZXRzL3lhbWwuanMvc3JjL1V0aWxzLmNvZmZlZSIsIi9Vc2Vycy9qZXJlbXlmYS9Eb2N1bWVudHMvUHJvamV0cy95YW1sLmpzL3NyYy9ZYW1sLmNvZmZlZSJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQ0NBLElBQUE7O0FBQUEsS0FBQSxHQUFVLE9BQUEsQ0FBUSxTQUFSOztBQUNWLE1BQUEsR0FBVSxPQUFBLENBQVEsVUFBUjs7QUFJSjs7O0VBR0YsTUFBQyxDQUFBLFdB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QU8sUUFBUCxFQUFpQixzQkFBakIsRUFBeUMsYUFBekM7QUFDSCxXQUFPLElBQUMsQ0FBQSxTQUFELENBQVcsSUFBWCxFQUFpQixRQUFqQixFQUEyQixzQkFBM0IsRUFBbUQsYUFBbkQ7RUFESjs7Ozs7OztFQUtYLE1BQU0sQ0FBRSxJQUFSLEdBQWU7OztBQUdmLElBQU8sZ0RBQVA7RUFDSSxJQUFDLENBQUEsSUFBRCxHQUFRLEtBRFo7OztBQUdBLE1BQU0sQ0FBQyxPQUFQLEdBQWlCIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsIlxuVXRpbHMgICA9IHJlcXVpcmUgJy4vVXRpbHMnXG5JbmxpbmUgID0gcmVxdWlyZSAnLi9JbmxpbmUnXG5cbiMgRHVtcGVyIGR1bXBzIEphdmFTY3JpcHQgdmFyaWFibGVzIHRvIFlBTUwgc3RyaW5ncy5cbiNcbmNsYXNzIER1bXBlclxuXG4gICAgIyBUaGUgYW1vdW50IG9mIHNwYWNlcyB0byB1c2UgZm9yIGluZGVudGF0aW9uIG9mIG5lc3RlZCBub2Rlcy5cbiAgICBAaW5kZW50YXRpb246ICAgNFxuXG5cbiAgICAjIER1bXBzIGEgSmF2YVNjcmlwdCB2YWx1ZSB0byBZQU1MLlxuICAgICNcbiAgICAjIEBwYXJhbSBbT2JqZWN0XSAgIGlucHV0ICAgICAgICAgICAgICAgICAgIFRoZSBKYXZhU2NyaXB0IHZhbHVlXG4gICAgIyBAcGFyYW0gW0ludGVnZXJdICBpbmxpbmUgICAgICAgICAgICAgICAgICBUaGUgbGV2ZWwgd2hlcmUgeW91IHN3aXRjaCB0byBpbmxpbmUgWUFNTFxuICAgICMgQHBhcmFtIFtJbnRlZ2VyXSAgaW5kZW50ICAgICAgICAgICAgICAgICAgVGhlIGxldmVsIG9mIGluZGVudGF0aW9uICh1c2VkIGludGVybmFsbHkpXG4gICAgIyBAcGFyYW0gW0Jvb2xlYW5dICBleGNlcHRpb25PbkludmFsaWRUeXBlICB0cnVlIGlmIGFuIGV4Y2VwdGlvbiBtdXN0IGJlIHRocm93biBvbiBpbnZhbGlkIHR5cGVzIChhIEphdmFTY3JpcHQgcmVzb3VyY2Ugb3Igb2JqZWN0KSwgZmFsc2Ugb3RoZXJ3aXNlXG4gICAgIyBAcGFyYW0gW0Z1bmN0aW9uXSBvYmplY3RFbmNvZGVyICAgICAgICAgICBBIGZ1bmN0aW9uIHRvIHNlcmlhbGl6ZSBjdXN0b20gb2JqZWN0cywgbnVsbCBvdGhlcndpc2VcbiAgICAjXG4gICAgIyBAcmV0dXJuIFtTdHJpbmddICBUaGUgWUFNTCByZXByZXNlbnRhdGlvbiBvZiB0aGUgSmF2YVNjcmlwdCB2YWx1ZVxuICAgICNcbiAgICBkdW1wOiAoaW5wdXQsIGlubGluZSA9IDAsIGluZGVudCA9IDAsIGV4Y2VwdGlvbk9uSW52YWxpZFR5cGUgPSBmYWxzZSwgb2JqZWN0RW5jb2RlciA9IG51bGwpIC0+XG4gICAgICAgIG91dHB1dCA9ICcnXG4gICAgICAgIHByZWZpeCA9IChpZiBpbmRlbnQgdGhlbiBVdGlscy5zdHJSZXBlYXQoJyAnLCBpbmRlbnQpIGVsc2UgJycpXG5cbiAgICAgICAgaWYgaW5saW5lIDw9IDAgb3IgdHlwZW9mKGlucHV0KSBpc250ICdvYmplY3QnIG9yIGlucHV0IGluc3RhbmNlb2YgRGF0ZSBvciBVdGlscy5pc0VtcHR5KGlucHV0KVxuICAgICAgICAgICAgb3V0cHV0ICs9IHByZWZpeCArIElubGluZS5kdW1wKGlucHV0LCBleGNlcHRpb25PbkludmFsaWRUeXBlLCBvYmplY3RFbmNvZGVyKVxuICAgICAgICBcbiAgICAgICAgZWxzZVxuICAgICAgICAgICAgaWYgaW5wdXQgaW5zdGFuY2VvZiBBcnJheVxuICAgICAgICAgICAgICAgIGZvciB2YWx1ZSBpbiBpbnB1dFxuICAgICAgICAgICAgICAgICAgICB3aWxsQmVJbmxpbmVkID0gKGlubGluZSAtIDEgPD0gMCBvciB0eXBlb2YodmFsdWUpIGlzbnQgJ29iamVjdCcgb3IgVXRpbHMuaXNFbXB0eSh2YWx1ZSkpXG5cbiAgICAgICAgICAgICAgICAgICAgb3V0cHV0ICs9XG4gICAgICAgICAgICAgICAgICAgICAgICBwcmVmaXggK1xuICAgICAgICAgICAgICAgICAgICAgICAgJy0nICtcbiAgICAgICAgICAgICAgICAgICAgICAgIChpZiB3aWxsQmVJbmxpbmVkIHRoZW4gJyAnIGVsc2UgXCJcXG5cIikgK1xuICAgICAgICAgICAgICAgICAgICAgICAgQGR1bXAodmFsdWUsIGlubGluZSAtIDEsIChpZiB3aWxsQmVJbmxpbmVkIHRoZW4gMCBlbHNlIGluZGVudCArIEBpbmRlbnRhdGlvbiksIGV4Y2VwdGlvbk9uSW52YWxpZFR5cGUsIG9iamVjdEVuY29kZXIpICtcbiAgICAgICAgICAgICAgICAgICAgICAgIChpZiB3aWxsQmVJbmxpbmVkIHRoZW4gXCJcXG5cIiBlbHNlICcnKVxuXG4gICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAgZm9yIGtleSwgdmFsdWUgb2YgaW5wdXRcbiAgICAgICAgICAgICAgICAgICAgd2lsbEJlSW5saW5lZCA9IChpbmxpbmUgLSAxIDw9IDAgb3IgdHlwZW9mKHZhbHVlKSBpc250ICdvYmplY3QnIG9yIFV0aWxzLmlzRW1wdHkodmFsdWUpKVxuXG4gICAgICAgICAgICAgICAgICAgIG91dHB1dCArPVxuICAgICAgICAgICAgICAgICAgICAgICAgcHJlZml4ICtcbiAgICAgICAgICAgICAgICAgICAgICAgIElubGluZS5kdW1wKGtleSwgZXhjZXB0aW9uT25JbnZhbGlkVHlwZSwgb2JqZWN0RW5jb2RlcikgKyAnOicgK1xuICAgICAgICAgICAgICAgICAgICAgICAgKGlmIHdpbGxCZUlubGluZWQgdGhlbiAnICcgZWxzZSBcIlxcblwiKSArXG4gICAgICAgICAgICAgICAgICAgICAgICBAZHVtcCh2YWx1ZSwgaW5saW5lIC0gMSwgKGlmIHdpbGxCZUlubGluZWQgdGhlbiAwIGVsc2UgaW5kZW50ICsgQGluZGVudGF0aW9uKSwgZXhjZXB0aW9uT25JbnZhbGlkVHlwZSwgb2JqZWN0RW5jb2RlcikgK1xuICAgICAgICAgICAgICAgICAgICAgICAgKGlmIHdpbGxCZUlubGluZWQgdGhlbiBcIlxcblwiIGVsc2UgJycpXG5cbiAgICAgICAgcmV0dXJuIG91dHB1dFxuXG5cbm1vZHVsZS5leHBvcnRzID0gRHVtcGVyXG4iLCJcblBhdHRlcm4gPSByZXF1aXJlICcuL1BhdHRlcm4nXG5cbiMgRXNjYXBlciBlbmNhcHN1bGF0ZXMgZXNjYXBpbmcgcnVsZXMgZm9yIHNpbmdsZVxuIyBhbmQgZG91YmxlLXF1b3RlZCBZQU1MIHN0cmluZ3MuXG5jbGFzcyBFc2NhcGVyXG5cbiAgICAjIE1hcHBpbmcgYXJyYXlzIGZvciBlc2NhcGluZyBhIGRvdWJsZSBxdW90ZWQgc3RyaW5nLiBUaGUgYmFja3NsYXNoIGlzXG4gICAgIyBmaXJzdCB0byBlbnN1cmUgcHJvcGVyIGVzY2FwaW5nLlxuICAgIEBMSVNUX0VTQ0FQRUVTOiAgICAgICAgICAgICAgICAgWydcXFxcJywgJ1xcXFxcXFxcJywgJ1xcXFxcIicsICdcIicsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXCJcXHgwMFwiLCAgXCJcXHgwMVwiLCAgXCJcXHgwMlwiLCAgXCJcXHgwM1wiLCAgXCJcXHgwNFwiLCAgXCJcXHgwNVwiLCAgXCJcXHgwNlwiLCAgXCJcXHgwN1wiLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwiXFx4MDhcIiwgIFwiXFx4MDlcIiwgIFwiXFx4MGFcIiwgIFwiXFx4MGJcIiwgIFwiXFx4MGNcIiwgIFwiXFx4MGRcIiwgIFwiXFx4MGVcIiwgIFwiXFx4MGZcIixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcIlxceDEwXCIsICBcIlxceDExXCIsICBcIlxceDEyXCIsICBcIlxceDEzXCIsICBcIlxceDE0XCIsICBcIlxceDE1XCIsICBcIlxceDE2XCIsICBcIlxceDE3XCIsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXCJcXHgxOFwiLCAgXCJcXHgxOVwiLCAgXCJcXHgxYVwiLCAgXCJcXHgxYlwiLCAgXCJcXHgxY1wiLCAgXCJcXHgxZFwiLCAgXCJcXHgxZVwiLCAgXCJcXHgxZlwiLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChjaCA9IFN0cmluZy5mcm9tQ2hhckNvZGUpKDB4MDA4NSksIGNoKDB4MDBBMCksIGNoKDB4MjAyOCksIGNoKDB4MjAyOSldXG4gICAgQExJU1RfRVNDQVBFRDogICAgICAgICAgICAgICAgICBbJ1xcXFxcXFxcJywgJ1xcXFxcIicsICdcXFxcXCInLCAnXFxcXFwiJyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcIlxcXFwwXCIsICAgXCJcXFxceDAxXCIsIFwiXFxcXHgwMlwiLCBcIlxcXFx4MDNcIiwgXCJcXFxceDA0XCIsIFwiXFxcXHgwNVwiLCBcIlxcXFx4MDZcIiwgXCJcXFxcYVwiLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwiXFxcXGJcIiwgICBcIlxcXFx0XCIsICAgXCJcXFxcblwiLCAgIFwiXFxcXHZcIiwgICBcIlxcXFxmXCIsICAgXCJcXFxcclwiLCAgIFwiXFxcXHgwZVwiLCBcIlxcXFx4MGZcIixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcIlxcXFx4MTBcIiwgXCJcXFxceDExXCIsIFwiXFxcXHgxMlwiLCBcIlxcXFx4MTNcIiwgXCJcXFxceDE0XCIsIFwiXFxcXHgxNVwiLCBcIlxcXFx4MTZcIiwgXCJcXFxceDE3XCIsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXCJcXFxceDE4XCIsIFwiXFxcXHgxOVwiLCBcIlxcXFx4MWFcIiwgXCJcXFxcZVwiLCAgIFwiXFxcXHgxY1wiLCBcIlxcXFx4MWRcIiwgXCJcXFxceDFlXCIsIFwiXFxcXHgxZlwiLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwiXFxcXE5cIiwgXCJcXFxcX1wiLCBcIlxcXFxMXCIsIFwiXFxcXFBcIl1cblxuICAgIEBNQVBQSU5HX0VTQ0FQRUVTX1RPX0VTQ0FQRUQ6ICAgZG8gPT5cbiAgICAgICAgbWFwcGluZyA9IHt9XG4gICAgICAgIGZvciBpIGluIFswLi4uQExJU1RfRVNDQVBFRVMubGVuZ3RoXVxuICAgICAgICAgICAgbWFwcGluZ1tATElTVF9FU0NBUEVFU1tpXV0gPSBATElTVF9FU0NBUEVEW2ldXG4gICAgICAgIHJldHVybiBtYXBwaW5nXG5cbiAgICAjIENoYXJhY3RlcnMgdGhhdCB3b3VsZCBjYXVzZSBhIGR1bXBlZCBzdHJpbmcgdG8gcmVxdWlyZSBkb3VibGUgcXVvdGluZy5cbiAgICBAUEFUVEVSTl9DSEFSQUNURVJTX1RPX0VTQ0FQRTogIG5ldyBQYXR0ZXJuICdbXFxcXHgwMC1cXFxceDFmXXxcXHhjMlxceDg1fFxceGMyXFx4YTB8XFx4ZTJcXHg4MFxceGE4fFxceGUyXFx4ODBcXHhhOSdcblxuICAgICMgT3RoZXIgcHJlY29tcGlsZWQgcGF0dGVybnNcbiAgICBAUEFUVEVSTl9NQVBQSU5HX0VTQ0FQRUVTOiAgICAgIG5ldyBQYXR0ZXJuIEBMSVNUX0VTQ0FQRUVTLmpvaW4oJ3wnKS5zcGxpdCgnXFxcXCcpLmpvaW4oJ1xcXFxcXFxcJylcbiAgICBAUEFUVEVSTl9TSU5HTEVfUVVPVElORzogICAgICAgIG5ldyBQYXR0ZXJuICdbXFxcXHNcXCdcIjp7fVtcXFxcXSwmKiM/XXxeWy0/fDw+PSElQGBdJ1xuXG5cblxuICAgICMgRGV0ZXJtaW5lcyBpZiBhIEphdmFTY3JpcHQgdmFsdWUgd291bGQgcmVxdWlyZSBkb3VibGUgcXVvdGluZyBpbiBZQU1MLlxuICAgICNcbiAgICAjIEBwYXJhbSBbU3RyaW5nXSAgIHZhbHVlICAgQSBKYXZhU2NyaXB0IHZhbHVlIHZhbHVlXG4gICAgI1xuICAgICMgQHJldHVybiBbQm9vbGVhbl0gdHJ1ZSAgICBpZiB0aGUgdmFsdWUgd291bGQgcmVxdWlyZSBkb3VibGUgcXVvdGVzLlxuICAgICNcbiAgICBAcmVxdWlyZXNEb3VibGVRdW90aW5nOiAodmFsdWUpIC0+XG4gICAgICAgIHJldHVybiBAUEFUVEVSTl9DSEFSQUNURVJTX1RPX0VTQ0FQRS50ZXN0IHZhbHVlXG5cblxuICAgICMgRXNjYXBlcyBhbmQgc3Vycm91bmRzIGEgSmF2YVNjcmlwdCB2YWx1ZSB3aXRoIGRvdWJsZSBxdW90ZXMuXG4gICAgI1xuICAgICMgQHBhcmFtIFtTdHJpbmddICAgdmFsdWUgICBBIEphdmFTY3JpcHQgdmFsdWVcbiAgICAjXG4gICAgIyBAcmV0dXJuIFtTdHJpbmddICBUaGUgcXVvdGVkLCBlc2NhcGVkIHN0cmluZ1xuICAgICNcbiAgICBAZXNjYXBlV2l0aERvdWJsZVF1b3RlczogKHZhbHVlKSAtPlxuICAgICAgICByZXN1bHQgPSBAUEFUVEVSTl9NQVBQSU5HX0VTQ0FQRUVTLnJlcGxhY2UgdmFsdWUsIChzdHIpID0+XG4gICAgICAgICAgICByZXR1cm4gQE1BUFBJTkdfRVNDQVBFRVNfVE9fRVNDQVBFRFtzdHJdXG4gICAgICAgIHJldHVybiAnXCInK3Jlc3VsdCsnXCInXG5cblxuICAgICMgRGV0ZXJtaW5lcyBpZiBhIEphdmFTY3JpcHQgdmFsdWUgd291bGQgcmVxdWlyZSBzaW5nbGUgcXVvdGluZyBpbiBZQU1MLlxuICAgICNcbiAgICAjIEBwYXJhbSBbU3RyaW5nXSAgIHZhbHVlICAgQSBKYXZhU2NyaXB0IHZhbHVlXG4gICAgI1xuICAgICMgQHJldHVybiBbQm9vbGVhbl0gdHJ1ZSBpZiB0aGUgdmFsdWUgd291bGQgcmVxdWlyZSBzaW5nbGUgcXVvdGVzLlxuICAgICNcbiAgICBAcmVxdWlyZXNTaW5nbGVRdW90aW5nOiAodmFsdWUpIC0+XG4gICAgICAgIHJldHVybiBAUEFUVEVSTl9TSU5HTEVfUVVPVElORy50ZXN0IHZhbHVlXG5cblxuICAgICMgRXNjYXBlcyBhbmQgc3Vycm91bmRzIGEgSmF2YVNjcmlwdCB2YWx1ZSB3aXRoIHNpbmdsZSBxdW90ZXMuXG4gICAgI1xuICAgICMgQHBhcmFtIFtTdHJpbmddICAgdmFsdWUgICBBIEphdmFTY3JpcHQgdmFsdWVcbiAgICAjXG4gICAgIyBAcmV0dXJuIFtTdHJpbmddICBUaGUgcXVvdGVkLCBlc2NhcGVkIHN0cmluZ1xuICAgICNcbiAgICBAZXNjYXBlV2l0aFNpbmdsZVF1b3RlczogKHZhbHVlKSAtPlxuICAgICAgICByZXR1cm4gXCInXCIrdmFsdWUucmVwbGFjZSgvJy9nLCBcIicnXCIpK1wiJ1wiXG5cblxubW9kdWxlLmV4cG9ydHMgPSBFc2NhcGVyXG4iLCJcbmNsYXNzIER1bXBFeGNlcHRpb24gZXh0ZW5kcyBFcnJvclxuXG4gICAgY29uc3RydWN0b3I6IChAbWVzc2FnZSwgQHBhcnNlZExpbmUsIEBzbmlwcGV0KSAtPlxuXG4gICAgdG9TdHJpbmc6IC0+XG4gICAgICAgIGlmIEBwYXJzZWRMaW5lPyBhbmQgQHNuaXBwZXQ/XG4gICAgICAgICAgICByZXR1cm4gJzxEdW1wRXhjZXB0aW9uPiAnICsgQG1lc3NhZ2UgKyAnIChsaW5lICcgKyBAcGFyc2VkTGluZSArICc6IFxcJycgKyBAc25pcHBldCArICdcXCcpJ1xuICAgICAgICBlbHNlXG4gICAgICAgICAgICByZXR1cm4gJzxEdW1wRXhjZXB0aW9uPiAnICsgQG1lc3NhZ2VcblxubW9kdWxlLmV4cG9ydHMgPSBEdW1wRXhjZXB0aW9uXG4iLCJcbmNsYXNzIFBhcnNlRXhjZXB0aW9uIGV4dGVuZHMgRXJyb3JcblxuICAgIGNvbnN0cnVjdG9yOiAoQG1lc3NhZ2UsIEBwYXJzZWRMaW5lLCBAc25pcHBldCkgLT5cblxuICAgIHRvU3RyaW5nOiAtPlxuICAgICAgICBpZiBAcGFyc2VkTGluZT8gYW5kIEBzbmlwcGV0P1xuICAgICAgICAgICAgcmV0dXJuICc8UGFyc2VFeGNlcHRpb24+ICcgKyBAbWVzc2FnZSArICcgKGxpbmUgJyArIEBwYXJzZWRMaW5lICsgJzogXFwnJyArIEBzbmlwcGV0ICsgJ1xcJyknXG4gICAgICAgIGVsc2VcbiAgICAgICAgICAgIHJldHVybiAnPFBhcnNlRXhjZXB0aW9uPiAnICsgQG1lc3NhZ2VcblxubW9kdWxlLmV4cG9ydHMgPSBQYXJzZUV4Y2VwdGlvblxuIiwiXG5QYXR0ZXJuICAgICAgICAgPSByZXF1aXJlICcuL1BhdHRlcm4nXG5VbmVzY2FwZXIgICAgICAgPSByZXF1aXJlICcuL1VuZXNjYXBlcidcbkVzY2FwZXIgICAgICAgICA9IHJlcXVpcmUgJy4vRXNjYXBlcidcblV0aWxzICAgICAgICAgICA9IHJlcXVpcmUgJy4vVXRpbHMnXG5QYXJzZUV4Y2VwdGlvbiAgPSByZXF1aXJlICcuL0V4Y2VwdGlvbi9QYXJzZUV4Y2VwdGlvbidcbkR1bXBFeGNlcHRpb24gICA9IHJlcXVpcmUgJy4vRXhjZXB0aW9uL0R1bXBFeGNlcHRpb24nXG5cbiMgSW5saW5lIFlBTUwgcGFyc2luZyBhbmQgZHVtcGluZ1xuY2xhc3MgSW5saW5lXG5cbiAgICAjIFF1b3RlZCBzdHJpbmcgcmVndWxhciBleHByZXNzaW9uXG4gICAgQFJFR0VYX1FVT1RFRF9TVFJJTkc6ICAgICAgICAgICAgICAgJyg/OlwiKD86W15cIlxcXFxcXFxcXSooPzpcXFxcXFxcXC5bXlwiXFxcXFxcXFxdKikqKVwifFxcJyg/OlteXFwnXSooPzpcXCdcXCdbXlxcJ10qKSopXFwnKSdcblxuICAgICMgUHJlLWNvbXBpbGVkIHBhdHRlcm5zXG4gICAgI1xuICAgIEBQQVRURVJOX1RSQUlMSU5HX0NPTU1FTlRTOiAgICAgICAgIG5ldyBQYXR0ZXJuICdeXFxcXHMqIy4qJCdcbiAgICBAUEFUVEVSTl9RVU9URURfU0NBTEFSOiAgICAgICAgICAgICBuZXcgUGF0dGVybiAnXicrQFJFR0VYX1FVT1RFRF9TVFJJTkdcbiAgICBAUEFUVEVSTl9USE9VU0FORF9OVU1FUklDX1NDQUxBUjogICBuZXcgUGF0dGVybiAnXigtfFxcXFwrKT9bMC05LF0rKFxcXFwuWzAtOV0rKT8kJ1xuICAgIEBQQVRURVJOX1NDQUxBUl9CWV9ERUxJTUlURVJTOiAgICAgIHt9XG5cbiAgICAjIFNldHRpbmdzXG4gICAgQHNldHRpbmdzOiB7fVxuXG5cbiAgICAjIENvbmZpZ3VyZSBZQU1MIGlubGluZS5cbiAgICAjXG4gICAgIyBAcGFyYW0gW0Jvb2xlYW5dICBleGNlcHRpb25PbkludmFsaWRUeXBlICB0cnVlIGlmIGFuIGV4Y2VwdGlvbiBtdXN0IGJlIHRocm93biBvbiBpbnZhbGlkIHR5cGVzIChhIEphdmFTY3JpcHQgcmVzb3VyY2Ugb3Igb2JqZWN0KSwgZmFsc2Ugb3RoZXJ3aXNlXG4gICAgIyBAcGFyYW0gW0Z1bmN0aW9uXSBvYmplY3REZWNvZGVyICAgICAgICAgICBBIGZ1bmN0aW9uIHRvIGRlc2VyaWFsaXplIGN1c3RvbSBvYmplY3RzLCBudWxsIG90aGVyd2lzZVxuICAgICNcbiAgICBAY29uZmlndXJlOiAoZXhjZXB0aW9uT25JbnZhbGlkVHlwZSA9IG51bGwsIG9iamVjdERlY29kZXIgPSBudWxsKSAtPlxuICAgICAgICAjIFVwZGF0ZSBzZXR0aW5nc1xuICAgICAgICBAc2V0dGluZ3MuZXhjZXB0aW9uT25JbnZhbGlkVHlwZSA9IGV4Y2VwdGlvbk9uSW52YWxpZFR5cGVcbiAgICAgICAgQHNldHRpbmdzLm9iamVjdERlY29kZXIgPSBvYmplY3REZWNvZGVyXG4gICAgICAgIHJldHVyblxuXG5cbiAgICAjIENvbnZlcnRzIGEgWUFNTCBzdHJpbmcgdG8gYSBKYXZhU2NyaXB0IG9iamVjdC5cbiAgICAjXG4gICAgIyBAcGFyYW0gW1N0cmluZ10gICB2YWx1ZSAgICAgICAgICAgICAgICAgICBBIFlBTUwgc3RyaW5nXG4gICAgIyBAcGFyYW0gW0Jvb2xlYW5dICBleGNlcHRpb25PbkludmFsaWRUeXBlICB0cnVlIGlmIGFuIGV4Y2VwdGlvbiBtdXN0IGJlIHRocm93biBvbiBpbnZhbGlkIHR5cGVzIChhIEphdmFTY3JpcHQgcmVzb3VyY2Ugb3Igb2JqZWN0KSwgZmFsc2Ugb3RoZXJ3aXNlXG4gICAgIyBAcGFyYW0gW0Z1bmN0aW9uXSBvYmplY3REZWNvZGVyICAgICAgICAgICBBIGZ1bmN0aW9uIHRvIGRlc2VyaWFsaXplIGN1c3RvbSBvYmplY3RzLCBudWxsIG90aGVyd2lzZVxuICAgICNcbiAgICAjIEByZXR1cm4gW09iamVjdF0gIEEgSmF2YVNjcmlwdCBvYmplY3QgcmVwcmVzZW50aW5nIHRoZSBZQU1MIHN0cmluZ1xuICAgICNcbiAgICAjIEB0aHJvdyBbUGFyc2VFeGNlcHRpb25dXG4gICAgI1xuICAgIEBwYXJzZTogKHZhbHVlLCBleGNlcHRpb25PbkludmFsaWRUeXBlID0gZmFsc2UsIG9iamVjdERlY29kZXIgPSBudWxsKSAtPlxuICAgICAgICAjIFVwZGF0ZSBzZXR0aW5ncyBmcm9tIGxhc3QgY2FsbCBvZiBJbmxpbmUucGFyc2UoKVxuICAgICAgICBAc2V0dGluZ3MuZXhjZXB0aW9uT25JbnZhbGlkVHlwZSA9IGV4Y2VwdGlvbk9uSW52YWxpZFR5cGVcbiAgICAgICAgQHNldHRpbmdzLm9iamVjdERlY29kZXIgPSBvYmplY3REZWNvZGVyXG5cbiAgICAgICAgaWYgbm90IHZhbHVlP1xuICAgICAgICAgICAgcmV0dXJuICcnXG5cbiAgICAgICAgdmFsdWUgPSBVdGlscy50cmltIHZhbHVlXG5cbiAgICAgICAgaWYgMCBpcyB2YWx1ZS5sZW5ndGhcbiAgICAgICAgICAgIHJldHVybiAnJ1xuXG4gICAgICAgICMgS2VlcCBhIGNvbnRleHQgb2JqZWN0IHRvIHBhc3MgdGhyb3VnaCBzdGF0aWMgbWV0aG9kc1xuICAgICAgICBjb250ZXh0ID0ge2V4Y2VwdGlvbk9uSW52YWxpZFR5cGUsIG9iamVjdERlY29kZXIsIGk6IDB9XG5cbiAgICAgICAgc3dpdGNoIHZhbHVlLmNoYXJBdCgwKVxuICAgICAgICAgICAgd2hlbiAnWydcbiAgICAgICAgICAgICAgICByZXN1bHQgPSBAcGFyc2VTZXF1ZW5jZSB2YWx1ZSwgY29udGV4dFxuICAgICAgICAgICAgICAgICsrY29udGV4dC5pXG4gICAgICAgICAgICB3aGVuICd7J1xuICAgICAgICAgICAgICAgIHJlc3VsdCA9IEBwYXJzZU1hcHBpbmcgdmFsdWUsIGNvbnRleHRcbiAgICAgICAgICAgICAgICArK2NvbnRleHQuaVxuICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAgICAgIHJlc3VsdCA9IEBwYXJzZVNjYWxhciB2YWx1ZSwgbnVsbCwgWydcIicsIFwiJ1wiXSwgY29udGV4dFxuXG4gICAgICAgICMgU29tZSBjb21tZW50cyBhcmUgYWxsb3dlZCBhdCB0aGUgZW5kXG4gICAgICAgIGlmIEBQQVRURVJOX1RSQUlMSU5HX0NPTU1FTlRTLnJlcGxhY2UodmFsdWVbY29udGV4dC5pLi5dLCAnJykgaXNudCAnJ1xuICAgICAgICAgICAgdGhyb3cgbmV3IFBhcnNlRXhjZXB0aW9uICdVbmV4cGVjdGVkIGNoYXJhY3RlcnMgbmVhciBcIicrdmFsdWVbY29udGV4dC5pLi5dKydcIi4nXG5cbiAgICAgICAgcmV0dXJuIHJlc3VsdFxuXG5cbiAgICAjIER1bXBzIGEgZ2l2ZW4gSmF2YVNjcmlwdCB2YXJpYWJsZSB0byBhIFlBTUwgc3RyaW5nLlxuICAgICNcbiAgICAjIEBwYXJhbSBbT2JqZWN0XSAgIHZhbHVlICAgICAgICAgICAgICAgICAgIFRoZSBKYXZhU2NyaXB0IHZhcmlhYmxlIHRvIGNvbnZlcnRcbiAgICAjIEBwYXJhbSBbQm9vbGVhbl0gIGV4Y2VwdGlvbk9uSW52YWxpZFR5cGUgIHRydWUgaWYgYW4gZXhjZXB0aW9uIG11c3QgYmUgdGhyb3duIG9uIGludmFsaWQgdHlwZXMgKGEgSmF2YVNjcmlwdCByZXNvdXJjZSBvciBvYmplY3QpLCBmYWxzZSBvdGhlcndpc2VcbiAgICAjIEBwYXJhbSBbRnVuY3Rpb25dIG9iamVjdEVuY29kZXIgICAgICAgICAgIEEgZnVuY3Rpb24gdG8gc2VyaWFsaXplIGN1c3RvbSBvYmplY3RzLCBudWxsIG90aGVyd2lzZVxuICAgICNcbiAgICAjIEByZXR1cm4gW1N0cmluZ10gIFRoZSBZQU1MIHN0cmluZyByZXByZXNlbnRpbmcgdGhlIEphdmFTY3JpcHQgb2JqZWN0XG4gICAgI1xuICAgICMgQHRocm93IFtEdW1wRXhjZXB0aW9uXVxuICAgICNcbiAgICBAZHVtcDogKHZhbHVlLCBleGNlcHRpb25PbkludmFsaWRUeXBlID0gZmFsc2UsIG9iamVjdEVuY29kZXIgPSBudWxsKSAtPlxuICAgICAgICBpZiBub3QgdmFsdWU/XG4gICAgICAgICAgICByZXR1cm4gJ251bGwnXG4gICAgICAgIHR5cGUgPSB0eXBlb2YgdmFsdWVcbiAgICAgICAgaWYgdHlwZSBpcyAnb2JqZWN0J1xuICAgICAgICAgICAgaWYgdmFsdWUgaW5zdGFuY2VvZiBEYXRlXG4gICAgICAgICAgICAgICAgcmV0dXJuIHZhbHVlLnRvSVNPU3RyaW5nKClcbiAgICAgICAgICAgIGVsc2UgaWYgb2JqZWN0RW5jb2Rlcj9cbiAgICAgICAgICAgICAgICByZXN1bHQgPSBvYmplY3RFbmNvZGVyIHZhbHVlXG4gICAgICAgICAgICAgICAgaWYgdHlwZW9mIHJlc3VsdCBpcyAnc3RyaW5nJyBvciByZXN1bHQ/XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiByZXN1bHRcbiAgICAgICAgICAgIHJldHVybiBAZHVtcE9iamVjdCB2YWx1ZVxuICAgICAgICBpZiB0eXBlIGlzICdib29sZWFuJ1xuICAgICAgICAgICAgcmV0dXJuIChpZiB2YWx1ZSB0aGVuICd0cnVlJyBlbHNlICdmYWxzZScpXG4gICAgICAgIGlmIFV0aWxzLmlzRGlnaXRzKHZhbHVlKVxuICAgICAgICAgICAgcmV0dXJuIChpZiB0eXBlIGlzICdzdHJpbmcnIHRoZW4gXCInXCIrdmFsdWUrXCInXCIgZWxzZSBTdHJpbmcocGFyc2VJbnQodmFsdWUpKSlcbiAgICAgICAgaWYgVXRpbHMuaXNOdW1lcmljKHZhbHVlKVxuICAgICAgICAgICAgcmV0dXJuIChpZiB0eXBlIGlzICdzdHJpbmcnIHRoZW4gXCInXCIrdmFsdWUrXCInXCIgZWxzZSBTdHJpbmcocGFyc2VGbG9hdCh2YWx1ZSkpKVxuICAgICAgICBpZiB0eXBlIGlzICdudW1iZXInXG4gICAgICAgICAgICByZXR1cm4gKGlmIHZhbHVlIGlzIEluZmluaXR5IHRoZW4gJy5JbmYnIGVsc2UgKGlmIHZhbHVlIGlzIC1JbmZpbml0eSB0aGVuICctLkluZicgZWxzZSAoaWYgaXNOYU4odmFsdWUpIHRoZW4gJy5OYU4nIGVsc2UgdmFsdWUpKSlcbiAgICAgICAgaWYgRXNjYXBlci5yZXF1aXJlc0RvdWJsZVF1b3RpbmcgdmFsdWVcbiAgICAgICAgICAgIHJldHVybiBFc2NhcGVyLmVzY2FwZVdpdGhEb3VibGVRdW90ZXMgdmFsdWVcbiAgICAgICAgaWYgRXNjYXBlci5yZXF1aXJlc1NpbmdsZVF1b3RpbmcgdmFsdWVcbiAgICAgICAgICAgIHJldHVybiBFc2NhcGVyLmVzY2FwZVdpdGhTaW5nbGVRdW90ZXMgdmFsdWVcbiAgICAgICAgaWYgJycgaXMgdmFsdWVcbiAgICAgICAgICAgIHJldHVybiAnXCJcIidcbiAgICAgICAgaWYgVXRpbHMuUEFUVEVSTl9EQVRFLnRlc3QgdmFsdWVcbiAgICAgICAgICAgIHJldHVybiBcIidcIit2YWx1ZStcIidcIjtcbiAgICAgICAgaWYgdmFsdWUudG9Mb3dlckNhc2UoKSBpbiBbJ251bGwnLCd+JywndHJ1ZScsJ2ZhbHNlJ11cbiAgICAgICAgICAgIHJldHVybiBcIidcIit2YWx1ZStcIidcIlxuICAgICAgICAjIERlZmF1bHRcbiAgICAgICAgcmV0dXJuIHZhbHVlO1xuXG5cbiAgICAjIER1bXBzIGEgSmF2YVNjcmlwdCBvYmplY3QgdG8gYSBZQU1MIHN0cmluZy5cbiAgICAjXG4gICAgIyBAcGFyYW0gW09iamVjdF0gICB2YWx1ZSAgICAgICAgICAgICAgICAgICBUaGUgSmF2YVNjcmlwdCBvYmplY3QgdG8gZHVtcFxuICAgICMgQHBhcmFtIFtCb29sZWFuXSAgZXhjZXB0aW9uT25JbnZhbGlkVHlwZSAgdHJ1ZSBpZiBhbiBleGNlcHRpb24gbXVzdCBiZSB0aHJvd24gb24gaW52YWxpZCB0eXBlcyAoYSBKYXZhU2NyaXB0IHJlc291cmNlIG9yIG9iamVjdCksIGZhbHNlIG90aGVyd2lzZVxuICAgICMgQHBhcmFtIFtGdW5jdGlvbl0gb2JqZWN0RW5jb2RlciAgICAgICAgICAgQSBmdW5jdGlvbiBkbyBzZXJpYWxpemUgY3VzdG9tIG9iamVjdHMsIG51bGwgb3RoZXJ3aXNlXG4gICAgI1xuICAgICMgQHJldHVybiBzdHJpbmcgVGhlIFlBTUwgc3RyaW5nIHJlcHJlc2VudGluZyB0aGUgSmF2YVNjcmlwdCBvYmplY3RcbiAgICAjXG4gICAgQGR1bXBPYmplY3Q6ICh2YWx1ZSwgZXhjZXB0aW9uT25JbnZhbGlkVHlwZSwgb2JqZWN0U3VwcG9ydCA9IG51bGwpIC0+XG4gICAgICAgICMgQXJyYXlcbiAgICAgICAgaWYgdmFsdWUgaW5zdGFuY2VvZiBBcnJheVxuICAgICAgICAgICAgb3V0cHV0ID0gW11cbiAgICAgICAgICAgIGZvciB2YWwgaW4gdmFsdWVcbiAgICAgICAgICAgICAgICBvdXRwdXQucHVzaCBAZHVtcCB2YWxcbiAgICAgICAgICAgIHJldHVybiAnWycrb3V0cHV0LmpvaW4oJywgJykrJ10nXG5cbiAgICAgICAgIyBNYXBwaW5nXG4gICAgICAgIGVsc2VcbiAgICAgICAgICAgIG91dHB1dCA9IFtdXG4gICAgICAgICAgICBmb3Iga2V5LCB2YWwgb2YgdmFsdWVcbiAgICAgICAgICAgICAgICBvdXRwdXQucHVzaCBAZHVtcChrZXkpKyc6ICcrQGR1bXAodmFsKVxuICAgICAgICAgICAgcmV0dXJuICd7JytvdXRwdXQuam9pbignLCAnKSsnfSdcblxuXG4gICAgIyBQYXJzZXMgYSBzY2FsYXIgdG8gYSBZQU1MIHN0cmluZy5cbiAgICAjXG4gICAgIyBAcGFyYW0gW09iamVjdF0gICBzY2FsYXJcbiAgICAjIEBwYXJhbSBbQXJyYXldICAgIGRlbGltaXRlcnNcbiAgICAjIEBwYXJhbSBbQXJyYXldICAgIHN0cmluZ0RlbGltaXRlcnNcbiAgICAjIEBwYXJhbSBbT2JqZWN0XSAgIGNvbnRleHRcbiAgICAjIEBwYXJhbSBbQm9vbGVhbl0gIGV2YWx1YXRlXG4gICAgI1xuICAgICMgQHJldHVybiBbU3RyaW5nXSAgQSBZQU1MIHN0cmluZ1xuICAgICNcbiAgICAjIEB0aHJvdyBbUGFyc2VFeGNlcHRpb25dIFdoZW4gbWFsZm9ybWVkIGlubGluZSBZQU1MIHN0cmluZyBpcyBwYXJzZWRcbiAgICAjXG4gICAgQHBhcnNlU2NhbGFyOiAoc2NhbGFyLCBkZWxpbWl0ZXJzID0gbnVsbCwgc3RyaW5nRGVsaW1pdGVycyA9IFsnXCInLCBcIidcIl0sIGNvbnRleHQgPSBudWxsLCBldmFsdWF0ZSA9IHRydWUpIC0+XG4gICAgICAgIHVubGVzcyBjb250ZXh0P1xuICAgICAgICAgICAgY29udGV4dCA9IGV4Y2VwdGlvbk9uSW52YWxpZFR5cGU6IEBzZXR0aW5ncy5leGNlcHRpb25PbkludmFsaWRUeXBlLCBvYmplY3REZWNvZGVyOiBAc2V0dGluZ3Mub2JqZWN0RGVjb2RlciwgaTogMFxuICAgICAgICB7aX0gPSBjb250ZXh0XG5cbiAgICAgICAgaWYgc2NhbGFyLmNoYXJBdChpKSBpbiBzdHJpbmdEZWxpbWl0ZXJzXG4gICAgICAgICAgICAjIFF1b3RlZCBzY2FsYXJcbiAgICAgICAgICAgIG91dHB1dCA9IEBwYXJzZVF1b3RlZFNjYWxhciBzY2FsYXIsIGNvbnRleHRcbiAgICAgICAgICAgIHtpfSA9IGNvbnRleHRcblxuICAgICAgICAgICAgaWYgZGVsaW1pdGVycz9cbiAgICAgICAgICAgICAgICB0bXAgPSBVdGlscy5sdHJpbSBzY2FsYXJbaS4uXSwgJyAnXG4gICAgICAgICAgICAgICAgaWYgbm90KHRtcC5jaGFyQXQoMCkgaW4gZGVsaW1pdGVycylcbiAgICAgICAgICAgICAgICAgICAgdGhyb3cgbmV3IFBhcnNlRXhjZXB0aW9uICdVbmV4cGVjdGVkIGNoYXJhY3RlcnMgKCcrc2NhbGFyW2kuLl0rJykuJ1xuXG4gICAgICAgIGVsc2VcbiAgICAgICAgICAgICMgXCJub3JtYWxcIiBzdHJpbmdcbiAgICAgICAgICAgIGlmIG5vdCBkZWxpbWl0ZXJzXG4gICAgICAgICAgICAgICAgb3V0cHV0ID0gc2NhbGFyW2kuLl1cbiAgICAgICAgICAgICAgICBpICs9IG91dHB1dC5sZW5ndGhcblxuICAgICAgICAgICAgICAgICMgUmVtb3ZlIGNvbW1lbnRzXG4gICAgICAgICAgICAgICAgc3RycG9zID0gb3V0cHV0LmluZGV4T2YgJyAjJ1xuICAgICAgICAgICAgICAgIGlmIHN0cnBvcyBpc250IC0xXG4gICAgICAgICAgICAgICAgICAgIG91dHB1dCA9IFV0aWxzLnJ0cmltIG91dHB1dFswLi4uc3RycG9zXVxuXG4gICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAgam9pbmVkRGVsaW1pdGVycyA9IGRlbGltaXRlcnMuam9pbignfCcpXG4gICAgICAgICAgICAgICAgcGF0dGVybiA9IEBQQVRURVJOX1NDQUxBUl9CWV9ERUxJTUlURVJTW2pvaW5lZERlbGltaXRlcnNdXG4gICAgICAgICAgICAgICAgdW5sZXNzIHBhdHRlcm4/XG4gICAgICAgICAgICAgICAgICAgIHBhdHRlcm4gPSBuZXcgUGF0dGVybiAnXiguKz8pKCcram9pbmVkRGVsaW1pdGVycysnKSdcbiAgICAgICAgICAgICAgICAgICAgQFBBVFRFUk5fU0NBTEFSX0JZX0RFTElNSVRFUlNbam9pbmVkRGVsaW1pdGVyc10gPSBwYXR0ZXJuXG4gICAgICAgICAgICAgICAgaWYgbWF0Y2ggPSBwYXR0ZXJuLmV4ZWMgc2NhbGFyW2kuLl1cbiAgICAgICAgICAgICAgICAgICAgb3V0cHV0ID0gbWF0Y2hbMV1cbiAgICAgICAgICAgICAgICAgICAgaSArPSBvdXRwdXQubGVuZ3RoXG4gICAgICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAgICAgICAgICB0aHJvdyBuZXcgUGFyc2VFeGNlcHRpb24gJ01hbGZvcm1lZCBpbmxpbmUgWUFNTCBzdHJpbmcgKCcrc2NhbGFyKycpLidcblxuXG4gICAgICAgICAgICBpZiBldmFsdWF0ZVxuICAgICAgICAgICAgICAgIG91dHB1dCA9IEBldmFsdWF0ZVNjYWxhciBvdXRwdXQsIGNvbnRleHRcblxuICAgICAgICBjb250ZXh0LmkgPSBpXG4gICAgICAgIHJldHVybiBvdXRwdXRcblxuXG4gICAgIyBQYXJzZXMgYSBxdW90ZWQgc2NhbGFyIHRvIFlBTUwuXG4gICAgI1xuICAgICMgQHBhcmFtIFtTdHJpbmddICAgc2NhbGFyXG4gICAgIyBAcGFyYW0gW09iamVjdF0gICBjb250ZXh0XG4gICAgI1xuICAgICMgQHJldHVybiBbU3RyaW5nXSAgQSBZQU1MIHN0cmluZ1xuICAgICNcbiAgICAjIEB0aHJvdyBbUGFyc2VFeGNlcHRpb25dIFdoZW4gbWFsZm9ybWVkIGlubGluZSBZQU1MIHN0cmluZyBpcyBwYXJzZWRcbiAgICAjXG4gICAgQHBhcnNlUXVvdGVkU2NhbGFyOiAoc2NhbGFyLCBjb250ZXh0KSAtPlxuICAgICAgICB7aX0gPSBjb250ZXh0XG5cbiAgICAgICAgdW5sZXNzIG1hdGNoID0gQFBBVFRFUk5fUVVPVEVEX1NDQUxBUi5leGVjIHNjYWxhcltpLi5dXG4gICAgICAgICAgICB0aHJvdyBuZXcgUGFyc2VFeGNlcHRpb24gJ01hbGZvcm1lZCBpbmxpbmUgWUFNTCBzdHJpbmcgKCcrc2NhbGFyW2kuLl0rJykuJ1xuXG4gICAgICAgIG91dHB1dCA9IG1hdGNoWzBdLnN1YnN0cigxLCBtYXRjaFswXS5sZW5ndGggLSAyKVxuXG4gICAgICAgIGlmICdcIicgaXMgc2NhbGFyLmNoYXJBdChpKVxuICAgICAgICAgICAgb3V0cHV0ID0gVW5lc2NhcGVyLnVuZXNjYXBlRG91YmxlUXVvdGVkU3RyaW5nIG91dHB1dFxuICAgICAgICBlbHNlXG4gICAgICAgICAgICBvdXRwdXQgPSBVbmVzY2FwZXIudW5lc2NhcGVTaW5nbGVRdW90ZWRTdHJpbmcgb3V0cHV0XG5cbiAgICAgICAgaSArPSBtYXRjaFswXS5sZW5ndGhcblxuICAgICAgICBjb250ZXh0LmkgPSBpXG4gICAgICAgIHJldHVybiBvdXRwdXRcblxuXG4gICAgIyBQYXJzZXMgYSBzZXF1ZW5jZSB0byBhIFlBTUwgc3RyaW5nLlxuICAgICNcbiAgICAjIEBwYXJhbSBbU3RyaW5nXSAgIHNlcXVlbmNlXG4gICAgIyBAcGFyYW0gW09iamVjdF0gICBjb250ZXh0XG4gICAgI1xuICAgICMgQHJldHVybiBbU3RyaW5nXSAgQSBZQU1MIHN0cmluZ1xuICAgICNcbiAgICAjIEB0aHJvdyBbUGFyc2VFeGNlcHRpb25dIFdoZW4gbWFsZm9ybWVkIGlubGluZSBZQU1MIHN0cmluZyBpcyBwYXJzZWRcbiAgICAjXG4gICAgQHBhcnNlU2VxdWVuY2U6IChzZXF1ZW5jZSwgY29udGV4dCkgLT5cbiAgICAgICAgb3V0cHV0ID0gW11cbiAgICAgICAgbGVuID0gc2VxdWVuY2UubGVuZ3RoXG4gICAgICAgIHtpfSA9IGNvbnRleHRcbiAgICAgICAgaSArPSAxXG5cbiAgICAgICAgIyBbZm9vLCBiYXIsIC4uLl1cbiAgICAgICAgd2hpbGUgaSA8IGxlblxuICAgICAgICAgICAgY29udGV4dC5pID0gaVxuICAgICAgICAgICAgc3dpdGNoIHNlcXVlbmNlLmNoYXJBdChpKVxuICAgICAgICAgICAgICAgIHdoZW4gJ1snXG4gICAgICAgICAgICAgICAgICAgICMgTmVzdGVkIHNlcXVlbmNlXG4gICAgICAgICAgICAgICAgICAgIG91dHB1dC5wdXNoIEBwYXJzZVNlcXVlbmNlIHNlcXVlbmNlLCBjb250ZXh0XG4gICAgICAgICAgICAgICAgICAgIHtpfSA9IGNvbnRleHRcbiAgICAgICAgICAgICAgICB3aGVuICd7J1xuICAgICAgICAgICAgICAgICAgICAjIE5lc3RlZCBtYXBwaW5nXG4gICAgICAgICAgICAgICAgICAgIG91dHB1dC5wdXNoIEBwYXJzZU1hcHBpbmcgc2VxdWVuY2UsIGNvbnRleHRcbiAgICAgICAgICAgICAgICAgICAge2l9ID0gY29udGV4dFxuICAgICAgICAgICAgICAgIHdoZW4gJ10nXG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBvdXRwdXRcbiAgICAgICAgICAgICAgICB3aGVuICcsJywgJyAnLCBcIlxcblwiXG4gICAgICAgICAgICAgICAgICAgICMgRG8gbm90aGluZ1xuICAgICAgICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgICAgICAgICAgaXNRdW90ZWQgPSAoc2VxdWVuY2UuY2hhckF0KGkpIGluIFsnXCInLCBcIidcIl0pXG4gICAgICAgICAgICAgICAgICAgIHZhbHVlID0gQHBhcnNlU2NhbGFyIHNlcXVlbmNlLCBbJywnLCAnXSddLCBbJ1wiJywgXCInXCJdLCBjb250ZXh0XG4gICAgICAgICAgICAgICAgICAgIHtpfSA9IGNvbnRleHRcblxuICAgICAgICAgICAgICAgICAgICBpZiBub3QoaXNRdW90ZWQpIGFuZCB0eXBlb2YodmFsdWUpIGlzICdzdHJpbmcnIGFuZCAodmFsdWUuaW5kZXhPZignOiAnKSBpc250IC0xIG9yIHZhbHVlLmluZGV4T2YoXCI6XFxuXCIpIGlzbnQgLTEpXG4gICAgICAgICAgICAgICAgICAgICAgICAjIEVtYmVkZGVkIG1hcHBpbmc/XG4gICAgICAgICAgICAgICAgICAgICAgICB0cnlcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IEBwYXJzZU1hcHBpbmcgJ3snK3ZhbHVlKyd9J1xuICAgICAgICAgICAgICAgICAgICAgICAgY2F0Y2ggZVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgTm8sIGl0J3Mgbm90XG5cblxuICAgICAgICAgICAgICAgICAgICBvdXRwdXQucHVzaCB2YWx1ZVxuXG4gICAgICAgICAgICAgICAgICAgIC0taVxuXG4gICAgICAgICAgICArK2lcblxuICAgICAgICB0aHJvdyBuZXcgUGFyc2VFeGNlcHRpb24gJ01hbGZvcm1lZCBpbmxpbmUgWUFNTCBzdHJpbmcgJytzZXF1ZW5jZVxuXG5cbiAgICAjIFBhcnNlcyBhIG1hcHBpbmcgdG8gYSBZQU1MIHN0cmluZy5cbiAgICAjXG4gICAgIyBAcGFyYW0gW1N0cmluZ10gICBtYXBwaW5nXG4gICAgIyBAcGFyYW0gW09iamVjdF0gICBjb250ZXh0XG4gICAgI1xuICAgICMgQHJldHVybiBbU3RyaW5nXSAgQSBZQU1MIHN0cmluZ1xuICAgICNcbiAgICAjIEB0aHJvdyBbUGFyc2VFeGNlcHRpb25dIFdoZW4gbWFsZm9ybWVkIGlubGluZSBZQU1MIHN0cmluZyBpcyBwYXJzZWRcbiAgICAjXG4gICAgQHBhcnNlTWFwcGluZzogKG1hcHBpbmcsIGNvbnRleHQpIC0+XG4gICAgICAgIG91dHB1dCA9IHt9XG4gICAgICAgIGxlbiA9IG1hcHBpbmcubGVuZ3RoXG4gICAgICAgIHtpfSA9IGNvbnRleHRcbiAgICAgICAgaSArPSAxXG5cbiAgICAgICAgIyB7Zm9vOiBiYXIsIGJhcjpmb28sIC4uLn1cbiAgICAgICAgc2hvdWxkQ29udGludWVXaGlsZUxvb3AgPSBmYWxzZVxuICAgICAgICB3aGlsZSBpIDwgbGVuXG4gICAgICAgICAgICBjb250ZXh0LmkgPSBpXG4gICAgICAgICAgICBzd2l0Y2ggbWFwcGluZy5jaGFyQXQoaSlcbiAgICAgICAgICAgICAgICB3aGVuICcgJywgJywnLCBcIlxcblwiXG4gICAgICAgICAgICAgICAgICAgICsraVxuICAgICAgICAgICAgICAgICAgICBjb250ZXh0LmkgPSBpXG4gICAgICAgICAgICAgICAgICAgIHNob3VsZENvbnRpbnVlV2hpbGVMb29wID0gdHJ1ZVxuICAgICAgICAgICAgICAgIHdoZW4gJ30nXG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBvdXRwdXRcblxuICAgICAgICAgICAgaWYgc2hvdWxkQ29udGludWVXaGlsZUxvb3BcbiAgICAgICAgICAgICAgICBzaG91bGRDb250aW51ZVdoaWxlTG9vcCA9IGZhbHNlXG4gICAgICAgICAgICAgICAgY29udGludWVcblxuICAgICAgICAgICAgIyBLZXlcbiAgICAgICAgICAgIGtleSA9IEBwYXJzZVNjYWxhciBtYXBwaW5nLCBbJzonLCAnICcsIFwiXFxuXCJdLCBbJ1wiJywgXCInXCJdLCBjb250ZXh0LCBmYWxzZVxuICAgICAgICAgICAge2l9ID0gY29udGV4dFxuXG4gICAgICAgICAgICAjIFZhbHVlXG4gICAgICAgICAgICBkb25lID0gZmFsc2VcblxuICAgICAgICAgICAgd2hpbGUgaSA8IGxlblxuICAgICAgICAgICAgICAgIGNvbnRleHQuaSA9IGlcbiAgICAgICAgICAgICAgICBzd2l0Y2ggbWFwcGluZy5jaGFyQXQoaSlcbiAgICAgICAgICAgICAgICAgICAgd2hlbiAnWydcbiAgICAgICAgICAgICAgICAgICAgICAgICMgTmVzdGVkIHNlcXVlbmNlXG4gICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IEBwYXJzZVNlcXVlbmNlIG1hcHBpbmcsIGNvbnRleHRcbiAgICAgICAgICAgICAgICAgICAgICAgIHtpfSA9IGNvbnRleHRcbiAgICAgICAgICAgICAgICAgICAgICAgICMgU3BlYzogS2V5cyBNVVNUIGJlIHVuaXF1ZTsgZmlyc3Qgb25lIHdpbnMuXG4gICAgICAgICAgICAgICAgICAgICAgICAjIFBhcnNlciBjYW5ub3QgYWJvcnQgdGhpcyBtYXBwaW5nIGVhcmxpZXIsIHNpbmNlIGxpbmVzXG4gICAgICAgICAgICAgICAgICAgICAgICAjIGFyZSBwcm9jZXNzZWQgc2VxdWVudGlhbGx5LlxuICAgICAgICAgICAgICAgICAgICAgICAgaWYgb3V0cHV0W2tleV0gPT0gdW5kZWZpbmVkXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0cHV0W2tleV0gPSB2YWx1ZVxuICAgICAgICAgICAgICAgICAgICAgICAgZG9uZSA9IHRydWVcbiAgICAgICAgICAgICAgICAgICAgd2hlbiAneydcbiAgICAgICAgICAgICAgICAgICAgICAgICMgTmVzdGVkIG1hcHBpbmdcbiAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gQHBhcnNlTWFwcGluZyBtYXBwaW5nLCBjb250ZXh0XG4gICAgICAgICAgICAgICAgICAgICAgICB7aX0gPSBjb250ZXh0XG4gICAgICAgICAgICAgICAgICAgICAgICAjIFNwZWM6IEtleXMgTVVTVCBiZSB1bmlxdWU7IGZpcnN0IG9uZSB3aW5zLlxuICAgICAgICAgICAgICAgICAgICAgICAgIyBQYXJzZXIgY2Fubm90IGFib3J0IHRoaXMgbWFwcGluZyBlYXJsaWVyLCBzaW5jZSBsaW5lc1xuICAgICAgICAgICAgICAgICAgICAgICAgIyBhcmUgcHJvY2Vzc2VkIHNlcXVlbnRpYWxseS5cbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIG91dHB1dFtrZXldID09IHVuZGVmaW5lZFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dHB1dFtrZXldID0gdmFsdWVcbiAgICAgICAgICAgICAgICAgICAgICAgIGRvbmUgPSB0cnVlXG4gICAgICAgICAgICAgICAgICAgIHdoZW4gJzonLCAnICcsIFwiXFxuXCJcbiAgICAgICAgICAgICAgICAgICAgICAgICMgRG8gbm90aGluZ1xuICAgICAgICAgICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IEBwYXJzZVNjYWxhciBtYXBwaW5nLCBbJywnLCAnfSddLCBbJ1wiJywgXCInXCJdLCBjb250ZXh0XG4gICAgICAgICAgICAgICAgICAgICAgICB7aX0gPSBjb250ZXh0XG4gICAgICAgICAgICAgICAgICAgICAgICAjIFNwZWM6IEtleXMgTVVTVCBiZSB1bmlxdWU7IGZpcnN0IG9uZSB3aW5zLlxuICAgICAgICAgICAgICAgICAgICAgICAgIyBQYXJzZXIgY2Fubm90IGFib3J0IHRoaXMgbWFwcGluZyBlYXJsaWVyLCBzaW5jZSBsaW5lc1xuICAgICAgICAgICAgICAgICAgICAgICAgIyBhcmUgcHJvY2Vzc2VkIHNlcXVlbnRpYWxseS5cbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIG91dHB1dFtrZXldID09IHVuZGVmaW5lZFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dHB1dFtrZXldID0gdmFsdWVcbiAgICAgICAgICAgICAgICAgICAgICAgIGRvbmUgPSB0cnVlXG4gICAgICAgICAgICAgICAgICAgICAgICAtLWlcblxuICAgICAgICAgICAgICAgICsraVxuXG4gICAgICAgICAgICAgICAgaWYgZG9uZVxuICAgICAgICAgICAgICAgICAgICBicmVha1xuXG4gICAgICAgIHRocm93IG5ldyBQYXJzZUV4Y2VwdGlvbiAnTWFsZm9ybWVkIGlubGluZSBZQU1MIHN0cmluZyAnK21hcHBpbmdcblxuXG4gICAgIyBFdmFsdWF0ZXMgc2NhbGFycyBhbmQgcmVwbGFjZXMgbWFnaWMgdmFsdWVzLlxuICAgICNcbiAgICAjIEBwYXJhbSBbU3RyaW5nXSAgIHNjYWxhclxuICAgICNcbiAgICAjIEByZXR1cm4gW1N0cmluZ10gIEEgWUFNTCBzdHJpbmdcbiAgICAjXG4gICAgQGV2YWx1YXRlU2NhbGFyOiAoc2NhbGFyLCBjb250ZXh0KSAtPlxuICAgICAgICBzY2FsYXIgPSBVdGlscy50cmltKHNjYWxhcilcbiAgICAgICAgc2NhbGFyTG93ZXIgPSBzY2FsYXIudG9Mb3dlckNhc2UoKVxuXG4gICAgICAgIHN3aXRjaCBzY2FsYXJMb3dlclxuICAgICAgICAgICAgd2hlbiAnbnVsbCcsICcnLCAnfidcbiAgICAgICAgICAgICAgICByZXR1cm4gbnVsbFxuICAgICAgICAgICAgd2hlbiAndHJ1ZSdcbiAgICAgICAgICAgICAgICByZXR1cm4gdHJ1ZVxuICAgICAgICAgICAgd2hlbiAnZmFsc2UnXG4gICAgICAgICAgICAgICAgcmV0dXJuIGZhbHNlXG4gICAgICAgICAgICB3aGVuICcuaW5mJ1xuICAgICAgICAgICAgICAgIHJldHVybiBJbmZpbml0eVxuICAgICAgICAgICAgd2hlbiAnLm5hbidcbiAgICAgICAgICAgICAgICByZXR1cm4gTmFOXG4gICAgICAgICAgICB3aGVuICctLmluZidcbiAgICAgICAgICAgICAgICByZXR1cm4gSW5maW5pdHlcbiAgICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgICAgICBmaXJzdENoYXIgPSBzY2FsYXJMb3dlci5jaGFyQXQoMClcbiAgICAgICAgICAgICAgICBzd2l0Y2ggZmlyc3RDaGFyXG4gICAgICAgICAgICAgICAgICAgIHdoZW4gJyEnXG4gICAgICAgICAgICAgICAgICAgICAgICBmaXJzdFNwYWNlID0gc2NhbGFyLmluZGV4T2YoJyAnKVxuICAgICAgICAgICAgICAgICAgICAgICAgaWYgZmlyc3RTcGFjZSBpcyAtMVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpcnN0V29yZCA9IHNjYWxhckxvd2VyXG4gICAgICAgICAgICAgICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlyc3RXb3JkID0gc2NhbGFyTG93ZXJbMC4uLmZpcnN0U3BhY2VdXG4gICAgICAgICAgICAgICAgICAgICAgICBzd2l0Y2ggZmlyc3RXb3JkXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgd2hlbiAnISdcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgZmlyc3RTcGFjZSBpc250IC0xXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gcGFyc2VJbnQgQHBhcnNlU2NhbGFyKHNjYWxhclsyLi5dKVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gbnVsbFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoZW4gJyFzdHInXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBVdGlscy5sdHJpbSBzY2FsYXJbNC4uXVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoZW4gJyEhc3RyJ1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gVXRpbHMubHRyaW0gc2NhbGFyWzUuLl1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aGVuICchIWludCdcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHBhcnNlSW50KEBwYXJzZVNjYWxhcihzY2FsYXJbNS4uXSkpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgd2hlbiAnISFib29sJ1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gVXRpbHMucGFyc2VCb29sZWFuKEBwYXJzZVNjYWxhcihzY2FsYXJbNi4uXSksIGZhbHNlKVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoZW4gJyEhZmxvYXQnXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBwYXJzZUZsb2F0KEBwYXJzZVNjYWxhcihzY2FsYXJbNy4uXSkpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgd2hlbiAnISF0aW1lc3RhbXAnXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBVdGlscy5zdHJpbmdUb0RhdGUoVXRpbHMubHRyaW0oc2NhbGFyWzExLi5dKSlcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVubGVzcyBjb250ZXh0P1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udGV4dCA9IGV4Y2VwdGlvbk9uSW52YWxpZFR5cGU6IEBzZXR0aW5ncy5leGNlcHRpb25PbkludmFsaWRUeXBlLCBvYmplY3REZWNvZGVyOiBAc2V0dGluZ3Mub2JqZWN0RGVjb2RlciwgaTogMFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7b2JqZWN0RGVjb2RlciwgZXhjZXB0aW9uT25JbnZhbGlkVHlwZX0gPSBjb250ZXh0XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgb2JqZWN0RGVjb2RlclxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBJZiBvYmplY3REZWNvZGVyIGZ1bmN0aW9uIGlzIGdpdmVuLCB3ZSBjYW4gZG8gY3VzdG9tIGRlY29kaW5nIG9mIGN1c3RvbSB0eXBlc1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJpbW1lZFNjYWxhciA9IFV0aWxzLnJ0cmltIHNjYWxhclxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlyc3RTcGFjZSA9IHRyaW1tZWRTY2FsYXIuaW5kZXhPZignICcpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiBmaXJzdFNwYWNlIGlzIC0xXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIG9iamVjdERlY29kZXIgdHJpbW1lZFNjYWxhciwgbnVsbFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1YlZhbHVlID0gVXRpbHMubHRyaW0gdHJpbW1lZFNjYWxhcltmaXJzdFNwYWNlKzEuLl1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bmxlc3Mgc3ViVmFsdWUubGVuZ3RoID4gMFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdWJWYWx1ZSA9IG51bGxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gb2JqZWN0RGVjb2RlciB0cmltbWVkU2NhbGFyWzAuLi5maXJzdFNwYWNlXSwgc3ViVmFsdWVcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiBleGNlcHRpb25PbkludmFsaWRUeXBlXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aHJvdyBuZXcgUGFyc2VFeGNlcHRpb24gJ0N1c3RvbSBvYmplY3Qgc3VwcG9ydCB3aGVuIHBhcnNpbmcgYSBZQU1MIGZpbGUgaGFzIGJlZW4gZGlzYWJsZWQuJ1xuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBudWxsXG4gICAgICAgICAgICAgICAgICAgIHdoZW4gJzAnXG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAnMHgnIGlzIHNjYWxhclswLi4uMl1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gVXRpbHMuaGV4RGVjIHNjYWxhclxuICAgICAgICAgICAgICAgICAgICAgICAgZWxzZSBpZiBVdGlscy5pc0RpZ2l0cyBzY2FsYXJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gVXRpbHMub2N0RGVjIHNjYWxhclxuICAgICAgICAgICAgICAgICAgICAgICAgZWxzZSBpZiBVdGlscy5pc051bWVyaWMgc2NhbGFyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHBhcnNlRmxvYXQgc2NhbGFyXG4gICAgICAgICAgICAgICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHNjYWxhclxuICAgICAgICAgICAgICAgICAgICB3aGVuICcrJ1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYgVXRpbHMuaXNEaWdpdHMgc2NhbGFyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmF3ID0gc2NhbGFyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FzdCA9IHBhcnNlSW50KHJhdylcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiByYXcgaXMgU3RyaW5nKGNhc3QpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBjYXN0XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gcmF3XG4gICAgICAgICAgICAgICAgICAgICAgICBlbHNlIGlmIFV0aWxzLmlzTnVtZXJpYyBzY2FsYXJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gcGFyc2VGbG9hdCBzY2FsYXJcbiAgICAgICAgICAgICAgICAgICAgICAgIGVsc2UgaWYgQFBBVFRFUk5fVEhPVVNBTkRfTlVNRVJJQ19TQ0FMQVIudGVzdCBzY2FsYXJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gcGFyc2VGbG9hdChzY2FsYXIucmVwbGFjZSgnLCcsICcnKSlcbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBzY2FsYXJcbiAgICAgICAgICAgICAgICAgICAgd2hlbiAnLSdcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIFV0aWxzLmlzRGlnaXRzKHNjYWxhclsxLi5dKVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmICcwJyBpcyBzY2FsYXIuY2hhckF0KDEpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiAtVXRpbHMub2N0RGVjKHNjYWxhclsxLi5dKVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmF3ID0gc2NhbGFyWzEuLl1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FzdCA9IHBhcnNlSW50KHJhdylcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgcmF3IGlzIFN0cmluZyhjYXN0KVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIC1jYXN0XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiAtcmF3XG4gICAgICAgICAgICAgICAgICAgICAgICBlbHNlIGlmIFV0aWxzLmlzTnVtZXJpYyBzY2FsYXJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gcGFyc2VGbG9hdCBzY2FsYXJcbiAgICAgICAgICAgICAgICAgICAgICAgIGVsc2UgaWYgQFBBVFRFUk5fVEhPVVNBTkRfTlVNRVJJQ19TQ0FMQVIudGVzdCBzY2FsYXJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gcGFyc2VGbG9hdChzY2FsYXIucmVwbGFjZSgnLCcsICcnKSlcbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBzY2FsYXJcbiAgICAgICAgICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAgICAgICAgICAgICAgaWYgZGF0ZSA9IFV0aWxzLnN0cmluZ1RvRGF0ZShzY2FsYXIpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGRhdGVcbiAgICAgICAgICAgICAgICAgICAgICAgIGVsc2UgaWYgVXRpbHMuaXNOdW1lcmljKHNjYWxhcilcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gcGFyc2VGbG9hdCBzY2FsYXJcbiAgICAgICAgICAgICAgICAgICAgICAgIGVsc2UgaWYgQFBBVFRFUk5fVEhPVVNBTkRfTlVNRVJJQ19TQ0FMQVIudGVzdCBzY2FsYXJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gcGFyc2VGbG9hdChzY2FsYXIucmVwbGFjZSgnLCcsICcnKSlcbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBzY2FsYXJcblxubW9kdWxlLmV4cG9ydHMgPSBJbmxpbmVcbiIsIlxuSW5saW5lICAgICAgICAgID0gcmVxdWlyZSAnLi9JbmxpbmUnXG5QYXR0ZXJuICAgICAgICAgPSByZXF1aXJlICcuL1BhdHRlcm4nXG5VdGlscyAgICAgICAgICAgPSByZXF1aXJlICcuL1V0aWxzJ1xuUGFyc2VFeGNlcHRpb24gID0gcmVxdWlyZSAnLi9FeGNlcHRpb24vUGFyc2VFeGNlcHRpb24nXG5cbiMgUGFyc2VyIHBhcnNlcyBZQU1MIHN0cmluZ3MgdG8gY29udmVydCB0aGVtIHRvIEphdmFTY3JpcHQgb2JqZWN0cy5cbiNcbmNsYXNzIFBhcnNlclxuXG4gICAgIyBQcmUtY29tcGlsZWQgcGF0dGVybnNcbiAgICAjXG4gICAgUEFUVEVSTl9GT0xERURfU0NBTEFSX0FMTDogICAgICAgICAgICAgIG5ldyBQYXR0ZXJuICdeKD86KD88dHlwZT4hW15cXFxcfD5dKilcXFxccyspPyg/PHNlcGFyYXRvcj5cXFxcfHw+KSg/PG1vZGlmaWVycz5cXFxcK3xcXFxcLXxcXFxcZCt8XFxcXCtcXFxcZCt8XFxcXC1cXFxcZCt8XFxcXGQrXFxcXCt8XFxcXGQrXFxcXC0pPyg/PGNvbW1lbnRzPiArIy4qKT8kJ1xuICAgIFBBVFRFUk5fRk9MREVEX1NDQUxBUl9FTkQ6ICAgICAgICAgICAgICBuZXcgUGF0dGVybiAnKD88c2VwYXJhdG9yPlxcXFx8fD4pKD88bW9kaWZpZXJzPlxcXFwrfFxcXFwtfFxcXFxkK3xcXFxcK1xcXFxkK3xcXFxcLVxcXFxkK3xcXFxcZCtcXFxcK3xcXFxcZCtcXFxcLSk/KD88Y29tbWVudHM+ICsjLiopPyQnXG4gICAgUEFUVEVSTl9TRVFVRU5DRV9JVEVNOiAgICAgICAgICAgICAgICAgIG5ldyBQYXR0ZXJuICdeXFxcXC0oKD88bGVhZHNwYWNlcz5cXFxccyspKD88dmFsdWU+Lis/KSk/XFxcXHMqJCdcbiAgICBQQVRURVJOX0FOQ0hPUl9WQUxVRTogICAgICAgICAgICAgICAgICAgbmV3IFBhdHRlcm4gJ14mKD88cmVmPlteIF0rKSAqKD88dmFsdWU+LiopJ1xuICAgIFBBVFRFUk5fQ09NUEFDVF9OT1RBVElPTjogICAgICAgICAgICAgICBuZXcgUGF0dGVybiAnXig/PGtleT4nK0lubGluZS5SRUdFWF9RVU9URURfU1RSSU5HKyd8W14gXFwnXCJcXFxce1xcXFxbXS4qPykgKlxcXFw6KFxcXFxzKyg/PHZhbHVlPi4rPykpP1xcXFxzKiQnXG4gICAgUEFUVEVSTl9NQVBQSU5HX0lURU06ICAgICAgICAgICAgICAgICAgIG5ldyBQYXR0ZXJuICdeKD88a2V5PicrSW5saW5lLlJFR0VYX1FVT1RFRF9TVFJJTkcrJ3xbXiBcXCdcIlxcXFxbXFxcXHtdLio/KSAqXFxcXDooXFxcXHMrKD88dmFsdWU+Lis/KSk/XFxcXHMqJCdcbiAgICBQQVRURVJOX0RFQ0lNQUw6ICAgICAgICAgICAgICAgICAgICAgICAgbmV3IFBhdHRlcm4gJ1xcXFxkKydcbiAgICBQQVRURVJOX0lOREVOVF9TUEFDRVM6ICAgICAgICAgICAgICAgICAgbmV3IFBhdHRlcm4gJ14gKydcbiAgICBQQVRURVJOX1RSQUlMSU5HX0xJTkVTOiAgICAgICAgICAgICAgICAgbmV3IFBhdHRlcm4gJyhcXG4qKSQnXG4gICAgUEFUVEVSTl9ZQU1MX0hFQURFUjogICAgICAgICAgICAgICAgICAgIG5ldyBQYXR0ZXJuICdeXFxcXCVZQU1MWzogXVtcXFxcZFxcXFwuXSsuKlxcbidcbiAgICBQQVRURVJOX0xFQURJTkdfQ09NTUVOVFM6ICAgICAgICAgICAgICAgbmV3IFBhdHRlcm4gJ14oXFxcXCMuKj9cXG4pKydcbiAgICBQQVRURVJOX0RPQ1VNRU5UX01BUktFUl9TVEFSVDogICAgICAgICAgbmV3IFBhdHRlcm4gJ15cXFxcLVxcXFwtXFxcXC0uKj9cXG4nXG4gICAgUEFUVEVSTl9ET0NVTUVOVF9NQVJLRVJfRU5EOiAgICAgICAgICAgIG5ldyBQYXR0ZXJuICdeXFxcXC5cXFxcLlxcXFwuXFxcXHMqJCdcbiAgICBQQVRURVJOX0ZPTERFRF9TQ0FMQVJfQllfSU5ERU5UQVRJT046ICAge31cblxuICAgICMgQ29udGV4dCB0eXBlc1xuICAgICNcbiAgICBDT05URVhUX05PTkU6ICAgICAgIDBcbiAgICBDT05URVhUX1NFUVVFTkNFOiAgIDFcbiAgICBDT05URVhUX01BUFBJTkc6ICAgIDJcblxuXG4gICAgIyBDb25zdHJ1Y3RvclxuICAgICNcbiAgICAjIEBwYXJhbSBbSW50ZWdlcl0gIG9mZnNldCAgVGhlIG9mZnNldCBvZiBZQU1MIGRvY3VtZW50ICh1c2VkIGZvciBsaW5lIG51bWJlcnMgaW4gZXJyb3IgbWVzc2FnZXMpXG4gICAgI1xuICAgIGNvbnN0cnVjdG9yOiAoQG9mZnNldCA9IDApIC0+XG4gICAgICAgIEBsaW5lcyAgICAgICAgICA9IFtdXG4gICAgICAgIEBjdXJyZW50TGluZU5iICA9IC0xXG4gICAgICAgIEBjdXJyZW50TGluZSAgICA9ICcnXG4gICAgICAgIEByZWZzICAgICAgICAgICA9IHt9XG5cblxuICAgICMgUGFyc2VzIGEgWUFNTCBzdHJpbmcgdG8gYSBKYXZhU2NyaXB0IHZhbHVlLlxuICAgICNcbiAgICAjIEBwYXJhbSBbU3RyaW5nXSAgIHZhbHVlICAgICAgICAgICAgICAgICAgIEEgWUFNTCBzdHJpbmdcbiAgICAjIEBwYXJhbSBbQm9vbGVhbl0gIGV4Y2VwdGlvbk9uSW52YWxpZFR5cGUgIHRydWUgaWYgYW4gZXhjZXB0aW9uIG11c3QgYmUgdGhyb3duIG9uIGludmFsaWQgdHlwZXMgKGEgSmF2YVNjcmlwdCByZXNvdXJjZSBvciBvYmplY3QpLCBmYWxzZSBvdGhlcndpc2VcbiAgICAjIEBwYXJhbSBbRnVuY3Rpb25dIG9iamVjdERlY29kZXIgICAgICAgICAgIEEgZnVuY3Rpb24gdG8gZGVzZXJpYWxpemUgY3VzdG9tIG9iamVjdHMsIG51bGwgb3RoZXJ3aXNlXG4gICAgI1xuICAgICMgQHJldHVybiBbT2JqZWN0XSAgQSBKYXZhU2NyaXB0IHZhbHVlXG4gICAgI1xuICAgICMgQHRocm93IFtQYXJzZUV4Y2VwdGlvbl0gSWYgdGhlIFlBTUwgaXMgbm90IHZhbGlkXG4gICAgI1xuICAgIHBhcnNlOiAodmFsdWUsIGV4Y2VwdGlvbk9uSW52YWxpZFR5cGUgPSBmYWxzZSwgb2JqZWN0RGVjb2RlciA9IG51bGwpIC0+XG4gICAgICAgIEBjdXJyZW50TGluZU5iID0gLTFcbiAgICAgICAgQGN1cnJlbnRMaW5lID0gJydcbiAgICAgICAgQGxpbmVzID0gQGNsZWFudXAodmFsdWUpLnNwbGl0IFwiXFxuXCJcblxuICAgICAgICBkYXRhID0gbnVsbFxuICAgICAgICBjb250ZXh0ID0gQENPTlRFWFRfTk9ORVxuICAgICAgICBhbGxvd092ZXJ3cml0ZSA9IGZhbHNlXG4gICAgICAgIHdoaWxlIEBtb3ZlVG9OZXh0TGluZSgpXG4gICAgICAgICAgICBpZiBAaXNDdXJyZW50TGluZUVtcHR5KClcbiAgICAgICAgICAgICAgICBjb250aW51ZVxuXG4gICAgICAgICAgICAjIFRhYj9cbiAgICAgICAgICAgIGlmIFwiXFx0XCIgaXMgQGN1cnJlbnRMaW5lWzBdXG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IFBhcnNlRXhjZXB0aW9uICdBIFlBTUwgZmlsZSBjYW5ub3QgY29udGFpbiB0YWJzIGFzIGluZGVudGF0aW9uLicsIEBnZXRSZWFsQ3VycmVudExpbmVOYigpICsgMSwgQGN1cnJlbnRMaW5lXG5cbiAgICAgICAgICAgIGlzUmVmID0gbWVyZ2VOb2RlID0gZmFsc2VcbiAgICAgICAgICAgIGlmIHZhbHVlcyA9IEBQQVRURVJOX1NFUVVFTkNFX0lURU0uZXhlYyBAY3VycmVudExpbmVcbiAgICAgICAgICAgICAgICBpZiBAQ09OVEVYVF9NQVBQSU5HIGlzIGNvbnRleHRcbiAgICAgICAgICAgICAgICAgICAgdGhyb3cgbmV3IFBhcnNlRXhjZXB0aW9uICdZb3UgY2Fubm90IGRlZmluZSBhIHNlcXVlbmNlIGl0ZW0gd2hlbiBpbiBhIG1hcHBpbmcnXG4gICAgICAgICAgICAgICAgY29udGV4dCA9IEBDT05URVhUX1NFUVVFTkNFXG4gICAgICAgICAgICAgICAgZGF0YSA/PSBbXVxuXG4gICAgICAgICAgICAgICAgaWYgdmFsdWVzLnZhbHVlPyBhbmQgbWF0Y2hlcyA9IEBQQVRURVJOX0FOQ0hPUl9WQUxVRS5leGVjIHZhbHVlcy52YWx1ZVxuICAgICAgICAgICAgICAgICAgICBpc1JlZiA9IG1hdGNoZXMucmVmXG4gICAgICAgICAgICAgICAgICAgIHZhbHVlcy52YWx1ZSA9IG1hdGNoZXMudmFsdWVcblxuICAgICAgICAgICAgICAgICMgQXJyYXlcbiAgICAgICAgICAgICAgICBpZiBub3QodmFsdWVzLnZhbHVlPykgb3IgJycgaXMgVXRpbHMudHJpbSh2YWx1ZXMudmFsdWUsICcgJykgb3IgVXRpbHMubHRyaW0odmFsdWVzLnZhbHVlLCAnICcpLmluZGV4T2YoJyMnKSBpcyAwXG4gICAgICAgICAgICAgICAgICAgIGlmIEBjdXJyZW50TGluZU5iIDwgQGxpbmVzLmxlbmd0aCAtIDEgYW5kIG5vdCBAaXNOZXh0TGluZVVuSW5kZW50ZWRDb2xsZWN0aW9uKClcbiAgICAgICAgICAgICAgICAgICAgICAgIGMgPSBAZ2V0UmVhbEN1cnJlbnRMaW5lTmIoKSArIDFcbiAgICAgICAgICAgICAgICAgICAgICAgIHBhcnNlciA9IG5ldyBQYXJzZXIgY1xuICAgICAgICAgICAgICAgICAgICAgICAgcGFyc2VyLnJlZnMgPSBAcmVmc1xuICAgICAgICAgICAgICAgICAgICAgICAgZGF0YS5wdXNoIHBhcnNlci5wYXJzZShAZ2V0TmV4dEVtYmVkQmxvY2sobnVsbCwgdHJ1ZSksIGV4Y2VwdGlvbk9uSW52YWxpZFR5cGUsIG9iamVjdERlY29kZXIpXG4gICAgICAgICAgICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEucHVzaCBudWxsXG5cbiAgICAgICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAgICAgIGlmIHZhbHVlcy5sZWFkc3BhY2VzPy5sZW5ndGggYW5kIG1hdGNoZXMgPSBAUEFUVEVSTl9DT01QQUNUX05PVEFUSU9OLmV4ZWMgdmFsdWVzLnZhbHVlXG5cbiAgICAgICAgICAgICAgICAgICAgICAgICMgVGhpcyBpcyBhIGNvbXBhY3Qgbm90YXRpb24gZWxlbWVudCwgYWRkIHRvIG5leHQgYmxvY2sgYW5kIHBhcnNlXG4gICAgICAgICAgICAgICAgICAgICAgICBjID0gQGdldFJlYWxDdXJyZW50TGluZU5iKClcbiAgICAgICAgICAgICAgICAgICAgICAgIHBhcnNlciA9IG5ldyBQYXJzZXIgY1xuICAgICAgICAgICAgICAgICAgICAgICAgcGFyc2VyLnJlZnMgPSBAcmVmc1xuXG4gICAgICAgICAgICAgICAgICAgICAgICBibG9jayA9IHZhbHVlcy52YWx1ZVxuICAgICAgICAgICAgICAgICAgICAgICAgaW5kZW50ID0gQGdldEN1cnJlbnRMaW5lSW5kZW50YXRpb24oKVxuICAgICAgICAgICAgICAgICAgICAgICAgaWYgQGlzTmV4dExpbmVJbmRlbnRlZChmYWxzZSlcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBibG9jayArPSBcIlxcblwiK0BnZXROZXh0RW1iZWRCbG9jayhpbmRlbnQgKyB2YWx1ZXMubGVhZHNwYWNlcy5sZW5ndGggKyAxLCB0cnVlKVxuXG4gICAgICAgICAgICAgICAgICAgICAgICBkYXRhLnB1c2ggcGFyc2VyLnBhcnNlIGJsb2NrLCBleGNlcHRpb25PbkludmFsaWRUeXBlLCBvYmplY3REZWNvZGVyXG5cbiAgICAgICAgICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAgICAgICAgICAgICAgZGF0YS5wdXNoIEBwYXJzZVZhbHVlIHZhbHVlcy52YWx1ZSwgZXhjZXB0aW9uT25JbnZhbGlkVHlwZSwgb2JqZWN0RGVjb2RlclxuXG4gICAgICAgICAgICBlbHNlIGlmICh2YWx1ZXMgPSBAUEFUVEVSTl9NQVBQSU5HX0lURU0uZXhlYyBAY3VycmVudExpbmUpIGFuZCB2YWx1ZXMua2V5LmluZGV4T2YoJyAjJykgaXMgLTFcbiAgICAgICAgICAgICAgICBpZiBAQ09OVEVYVF9TRVFVRU5DRSBpcyBjb250ZXh0XG4gICAgICAgICAgICAgICAgICAgIHRocm93IG5ldyBQYXJzZUV4Y2VwdGlvbiAnWW91IGNhbm5vdCBkZWZpbmUgYSBtYXBwaW5nIGl0ZW0gd2hlbiBpbiBhIHNlcXVlbmNlJ1xuICAgICAgICAgICAgICAgIGNvbnRleHQgPSBAQ09OVEVYVF9NQVBQSU5HXG4gICAgICAgICAgICAgICAgZGF0YSA/PSB7fVxuXG4gICAgICAgICAgICAgICAgIyBGb3JjZSBjb3JyZWN0IHNldHRpbmdzXG4gICAgICAgICAgICAgICAgSW5saW5lLmNvbmZpZ3VyZSBleGNlcHRpb25PbkludmFsaWRUeXBlLCBvYmplY3REZWNvZGVyXG4gICAgICAgICAgICAgICAgdHJ5XG4gICAgICAgICAgICAgICAgICAgIGtleSA9IElubGluZS5wYXJzZVNjYWxhciB2YWx1ZXMua2V5XG4gICAgICAgICAgICAgICAgY2F0Y2ggZVxuICAgICAgICAgICAgICAgICAgICBlLnBhcnNlZExpbmUgPSBAZ2V0UmVhbEN1cnJlbnRMaW5lTmIoKSArIDFcbiAgICAgICAgICAgICAgICAgICAgZS5zbmlwcGV0ID0gQGN1cnJlbnRMaW5lXG5cbiAgICAgICAgICAgICAgICAgICAgdGhyb3cgZVxuXG4gICAgICAgICAgICAgICAgaWYgJzw8JyBpcyBrZXlcbiAgICAgICAgICAgICAgICAgICAgbWVyZ2VOb2RlID0gdHJ1ZVxuICAgICAgICAgICAgICAgICAgICBhbGxvd092ZXJ3cml0ZSA9IHRydWVcbiAgICAgICAgICAgICAgICAgICAgaWYgdmFsdWVzLnZhbHVlPy5pbmRleE9mKCcqJykgaXMgMFxuICAgICAgICAgICAgICAgICAgICAgICAgcmVmTmFtZSA9IHZhbHVlcy52YWx1ZVsxLi5dXG4gICAgICAgICAgICAgICAgICAgICAgICB1bmxlc3MgQHJlZnNbcmVmTmFtZV0/XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhyb3cgbmV3IFBhcnNlRXhjZXB0aW9uICdSZWZlcmVuY2UgXCInK3JlZk5hbWUrJ1wiIGRvZXMgbm90IGV4aXN0LicsIEBnZXRSZWFsQ3VycmVudExpbmVOYigpICsgMSwgQGN1cnJlbnRMaW5lXG5cbiAgICAgICAgICAgICAgICAgICAgICAgIHJlZlZhbHVlID0gQHJlZnNbcmVmTmFtZV1cblxuICAgICAgICAgICAgICAgICAgICAgICAgaWYgdHlwZW9mIHJlZlZhbHVlIGlzbnQgJ29iamVjdCdcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aHJvdyBuZXcgUGFyc2VFeGNlcHRpb24gJ1lBTUwgbWVyZ2Uga2V5cyB1c2VkIHdpdGggYSBzY2FsYXIgdmFsdWUgaW5zdGVhZCBvZiBhbiBvYmplY3QuJywgQGdldFJlYWxDdXJyZW50TGluZU5iKCkgKyAxLCBAY3VycmVudExpbmVcblxuICAgICAgICAgICAgICAgICAgICAgICAgaWYgcmVmVmFsdWUgaW5zdGFuY2VvZiBBcnJheVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgTWVyZ2UgYXJyYXkgd2l0aCBvYmplY3RcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3IgdmFsdWUsIGkgaW4gcmVmVmFsdWVcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YVtTdHJpbmcoaSldID89IHZhbHVlXG4gICAgICAgICAgICAgICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBNZXJnZSBvYmplY3RzXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgZm9yIGtleSwgdmFsdWUgb2YgcmVmVmFsdWVcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YVtrZXldID89IHZhbHVlXG5cbiAgICAgICAgICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAgICAgICAgICAgICAgaWYgdmFsdWVzLnZhbHVlPyBhbmQgdmFsdWVzLnZhbHVlIGlzbnQgJydcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IHZhbHVlcy52YWx1ZVxuICAgICAgICAgICAgICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gQGdldE5leHRFbWJlZEJsb2NrKClcblxuICAgICAgICAgICAgICAgICAgICAgICAgYyA9IEBnZXRSZWFsQ3VycmVudExpbmVOYigpICsgMVxuICAgICAgICAgICAgICAgICAgICAgICAgcGFyc2VyID0gbmV3IFBhcnNlciBjXG4gICAgICAgICAgICAgICAgICAgICAgICBwYXJzZXIucmVmcyA9IEByZWZzXG4gICAgICAgICAgICAgICAgICAgICAgICBwYXJzZWQgPSBwYXJzZXIucGFyc2UgdmFsdWUsIGV4Y2VwdGlvbk9uSW52YWxpZFR5cGVcblxuICAgICAgICAgICAgICAgICAgICAgICAgdW5sZXNzIHR5cGVvZiBwYXJzZWQgaXMgJ29iamVjdCdcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aHJvdyBuZXcgUGFyc2VFeGNlcHRpb24gJ1lBTUwgbWVyZ2Uga2V5cyB1c2VkIHdpdGggYSBzY2FsYXIgdmFsdWUgaW5zdGVhZCBvZiBhbiBvYmplY3QuJywgQGdldFJlYWxDdXJyZW50TGluZU5iKCkgKyAxLCBAY3VycmVudExpbmVcblxuICAgICAgICAgICAgICAgICAgICAgICAgaWYgcGFyc2VkIGluc3RhbmNlb2YgQXJyYXlcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIElmIHRoZSB2YWx1ZSBhc3NvY2lhdGVkIHdpdGggdGhlIG1lcmdlIGtleSBpcyBhIHNlcXVlbmNlLCB0aGVuIHRoaXMgc2VxdWVuY2UgaXMgZXhwZWN0ZWQgdG8gY29udGFpbiBtYXBwaW5nIG5vZGVzXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBhbmQgZWFjaCBvZiB0aGVzZSBub2RlcyBpcyBtZXJnZWQgaW4gdHVybiBhY2NvcmRpbmcgdG8gaXRzIG9yZGVyIGluIHRoZSBzZXF1ZW5jZS4gS2V5cyBpbiBtYXBwaW5nIG5vZGVzIGVhcmxpZXJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGluIHRoZSBzZXF1ZW5jZSBvdmVycmlkZSBrZXlzIHNwZWNpZmllZCBpbiBsYXRlciBtYXBwaW5nIG5vZGVzLlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvciBwYXJzZWRJdGVtIGluIHBhcnNlZFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bmxlc3MgdHlwZW9mIHBhcnNlZEl0ZW0gaXMgJ29iamVjdCdcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRocm93IG5ldyBQYXJzZUV4Y2VwdGlvbiAnTWVyZ2UgaXRlbXMgbXVzdCBiZSBvYmplY3RzLicsIEBnZXRSZWFsQ3VycmVudExpbmVOYigpICsgMSwgcGFyc2VkSXRlbVxuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIHBhcnNlZEl0ZW0gaW5zdGFuY2VvZiBBcnJheVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBNZXJnZSBhcnJheSB3aXRoIG9iamVjdFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZm9yIHZhbHVlLCBpIGluIHBhcnNlZEl0ZW1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrID0gU3RyaW5nKGkpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW5sZXNzIGRhdGEuaGFzT3duUHJvcGVydHkoaylcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YVtrXSA9IHZhbHVlXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgTWVyZ2Ugb2JqZWN0c1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZm9yIGtleSwgdmFsdWUgb2YgcGFyc2VkSXRlbVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVubGVzcyBkYXRhLmhhc093blByb3BlcnR5KGtleSlcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YVtrZXldID0gdmFsdWVcblxuICAgICAgICAgICAgICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgSWYgdGhlIHZhbHVlIGFzc29jaWF0ZWQgd2l0aCB0aGUga2V5IGlzIGEgc2luZ2xlIG1hcHBpbmcgbm9kZSwgZWFjaCBvZiBpdHMga2V5L3ZhbHVlIHBhaXJzIGlzIGluc2VydGVkIGludG8gdGhlXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBjdXJyZW50IG1hcHBpbmcsIHVubGVzcyB0aGUga2V5IGFscmVhZHkgZXhpc3RzIGluIGl0LlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvciBrZXksIHZhbHVlIG9mIHBhcnNlZFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bmxlc3MgZGF0YS5oYXNPd25Qcm9wZXJ0eShrZXkpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhW2tleV0gPSB2YWx1ZVxuXG4gICAgICAgICAgICAgICAgZWxzZSBpZiB2YWx1ZXMudmFsdWU/IGFuZCBtYXRjaGVzID0gQFBBVFRFUk5fQU5DSE9SX1ZBTFVFLmV4ZWMgdmFsdWVzLnZhbHVlXG4gICAgICAgICAgICAgICAgICAgIGlzUmVmID0gbWF0Y2hlcy5yZWZcbiAgICAgICAgICAgICAgICAgICAgdmFsdWVzLnZhbHVlID0gbWF0Y2hlcy52YWx1ZVxuXG5cbiAgICAgICAgICAgICAgICBpZiBtZXJnZU5vZGVcbiAgICAgICAgICAgICAgICAgICAgIyBNZXJnZSBrZXlzXG4gICAgICAgICAgICAgICAgZWxzZSBpZiBub3QodmFsdWVzLnZhbHVlPykgb3IgJycgaXMgVXRpbHMudHJpbSh2YWx1ZXMudmFsdWUsICcgJykgb3IgVXRpbHMubHRyaW0odmFsdWVzLnZhbHVlLCAnICcpLmluZGV4T2YoJyMnKSBpcyAwXG4gICAgICAgICAgICAgICAgICAgICMgSGFzaFxuICAgICAgICAgICAgICAgICAgICAjIGlmIG5leHQgbGluZSBpcyBsZXNzIGluZGVudGVkIG9yIGVxdWFsLCB0aGVuIGl0IG1lYW5zIHRoYXQgdGhlIGN1cnJlbnQgdmFsdWUgaXMgbnVsbFxuICAgICAgICAgICAgICAgICAgICBpZiBub3QoQGlzTmV4dExpbmVJbmRlbnRlZCgpKSBhbmQgbm90KEBpc05leHRMaW5lVW5JbmRlbnRlZENvbGxlY3Rpb24oKSlcbiAgICAgICAgICAgICAgICAgICAgICAgICMgU3BlYzogS2V5cyBNVVNUIGJlIHVuaXF1ZTsgZmlyc3Qgb25lIHdpbnMuXG4gICAgICAgICAgICAgICAgICAgICAgICAjIEJ1dCBvdmVyd3JpdGluZyBpcyBhbGxvd2VkIHdoZW4gYSBtZXJnZSBub2RlIGlzIHVzZWQgaW4gY3VycmVudCBibG9jay5cbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIGFsbG93T3ZlcndyaXRlIG9yIGRhdGFba2V5XSBpcyB1bmRlZmluZWRcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhW2tleV0gPSBudWxsXG5cbiAgICAgICAgICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAgICAgICAgICAgICAgYyA9IEBnZXRSZWFsQ3VycmVudExpbmVOYigpICsgMVxuICAgICAgICAgICAgICAgICAgICAgICAgcGFyc2VyID0gbmV3IFBhcnNlciBjXG4gICAgICAgICAgICAgICAgICAgICAgICBwYXJzZXIucmVmcyA9IEByZWZzXG4gICAgICAgICAgICAgICAgICAgICAgICB2YWwgPSBwYXJzZXIucGFyc2UgQGdldE5leHRFbWJlZEJsb2NrKCksIGV4Y2VwdGlvbk9uSW52YWxpZFR5cGUsIG9iamVjdERlY29kZXJcblxuICAgICAgICAgICAgICAgICAgICAgICAgIyBTcGVjOiBLZXlzIE1VU1QgYmUgdW5pcXVlOyBmaXJzdCBvbmUgd2lucy5cbiAgICAgICAgICAgICAgICAgICAgICAgICMgQnV0IG92ZXJ3cml0aW5nIGlzIGFsbG93ZWQgd2hlbiBhIG1lcmdlIG5vZGUgaXMgdXNlZCBpbiBjdXJyZW50IGJsb2NrLlxuICAgICAgICAgICAgICAgICAgICAgICAgaWYgYWxsb3dPdmVyd3JpdGUgb3IgZGF0YVtrZXldIGlzIHVuZGVmaW5lZFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFba2V5XSA9IHZhbFxuXG4gICAgICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAgICAgICAgICB2YWwgPSBAcGFyc2VWYWx1ZSB2YWx1ZXMudmFsdWUsIGV4Y2VwdGlvbk9uSW52YWxpZFR5cGUsIG9iamVjdERlY29kZXJcblxuICAgICAgICAgICAgICAgICAgICAjIFNwZWM6IEtleXMgTVVTVCBiZSB1bmlxdWU7IGZpcnN0IG9uZSB3aW5zLlxuICAgICAgICAgICAgICAgICAgICAjIEJ1dCBvdmVyd3JpdGluZyBpcyBhbGxvd2VkIHdoZW4gYSBtZXJnZSBub2RlIGlzIHVzZWQgaW4gY3VycmVudCBibG9jay5cbiAgICAgICAgICAgICAgICAgICAgaWYgYWxsb3dPdmVyd3JpdGUgb3IgZGF0YVtrZXldIGlzIHVuZGVmaW5lZFxuICAgICAgICAgICAgICAgICAgICAgICAgZGF0YVtrZXldID0gdmFsXG5cbiAgICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgICAgICAjIDEtbGluZXIgb3B0aW9uYWxseSBmb2xsb3dlZCBieSBuZXdsaW5lXG4gICAgICAgICAgICAgICAgbGluZUNvdW50ID0gQGxpbmVzLmxlbmd0aFxuICAgICAgICAgICAgICAgIGlmIDEgaXMgbGluZUNvdW50IG9yICgyIGlzIGxpbmVDb3VudCBhbmQgVXRpbHMuaXNFbXB0eShAbGluZXNbMV0pKVxuICAgICAgICAgICAgICAgICAgICB0cnlcbiAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gSW5saW5lLnBhcnNlIEBsaW5lc1swXSwgZXhjZXB0aW9uT25JbnZhbGlkVHlwZSwgb2JqZWN0RGVjb2RlclxuICAgICAgICAgICAgICAgICAgICBjYXRjaCBlXG4gICAgICAgICAgICAgICAgICAgICAgICBlLnBhcnNlZExpbmUgPSBAZ2V0UmVhbEN1cnJlbnRMaW5lTmIoKSArIDFcbiAgICAgICAgICAgICAgICAgICAgICAgIGUuc25pcHBldCA9IEBjdXJyZW50TGluZVxuXG4gICAgICAgICAgICAgICAgICAgICAgICB0aHJvdyBlXG5cbiAgICAgICAgICAgICAgICAgICAgaWYgdHlwZW9mIHZhbHVlIGlzICdvYmplY3QnXG4gICAgICAgICAgICAgICAgICAgICAgICBpZiB2YWx1ZSBpbnN0YW5jZW9mIEFycmF5XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlyc3QgPSB2YWx1ZVswXVxuICAgICAgICAgICAgICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvciBrZXkgb2YgdmFsdWVcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlyc3QgPSB2YWx1ZVtrZXldXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrXG5cbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIHR5cGVvZiBmaXJzdCBpcyAnc3RyaW5nJyBhbmQgZmlyc3QuaW5kZXhPZignKicpIGlzIDBcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gW11cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3IgYWxpYXMgaW4gdmFsdWVcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YS5wdXNoIEByZWZzW2FsaWFzWzEuLl1dXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBkYXRhXG5cbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHZhbHVlXG5cbiAgICAgICAgICAgICAgICBlbHNlIGlmIFV0aWxzLmx0cmltKHZhbHVlKS5jaGFyQXQoMCkgaW4gWydbJywgJ3snXVxuICAgICAgICAgICAgICAgICAgICB0cnlcbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBJbmxpbmUucGFyc2UgdmFsdWUsIGV4Y2VwdGlvbk9uSW52YWxpZFR5cGUsIG9iamVjdERlY29kZXJcbiAgICAgICAgICAgICAgICAgICAgY2F0Y2ggZVxuICAgICAgICAgICAgICAgICAgICAgICAgZS5wYXJzZWRMaW5lID0gQGdldFJlYWxDdXJyZW50TGluZU5iKCkgKyAxXG4gICAgICAgICAgICAgICAgICAgICAgICBlLnNuaXBwZXQgPSBAY3VycmVudExpbmVcblxuICAgICAgICAgICAgICAgICAgICAgICAgdGhyb3cgZVxuXG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IFBhcnNlRXhjZXB0aW9uICdVbmFibGUgdG8gcGFyc2UuJywgQGdldFJlYWxDdXJyZW50TGluZU5iKCkgKyAxLCBAY3VycmVudExpbmVcblxuICAgICAgICAgICAgaWYgaXNSZWZcbiAgICAgICAgICAgICAgICBpZiBkYXRhIGluc3RhbmNlb2YgQXJyYXlcbiAgICAgICAgICAgICAgICAgICAgQHJlZnNbaXNSZWZdID0gZGF0YVtkYXRhLmxlbmd0aC0xXVxuICAgICAgICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgICAgICAgICAgbGFzdEtleSA9IG51bGxcbiAgICAgICAgICAgICAgICAgICAgZm9yIGtleSBvZiBkYXRhXG4gICAgICAgICAgICAgICAgICAgICAgICBsYXN0S2V5ID0ga2V5XG4gICAgICAgICAgICAgICAgICAgIEByZWZzW2lzUmVmXSA9IGRhdGFbbGFzdEtleV1cblxuXG4gICAgICAgIGlmIFV0aWxzLmlzRW1wdHkoZGF0YSlcbiAgICAgICAgICAgIHJldHVybiBudWxsXG4gICAgICAgIGVsc2VcbiAgICAgICAgICAgIHJldHVybiBkYXRhXG5cblxuXG4gICAgIyBSZXR1cm5zIHRoZSBjdXJyZW50IGxpbmUgbnVtYmVyICh0YWtlcyB0aGUgb2Zmc2V0IGludG8gYWNjb3VudCkuXG4gICAgI1xuICAgICMgQHJldHVybiBbSW50ZWdlcl0gICAgIFRoZSBjdXJyZW50IGxpbmUgbnVtYmVyXG4gICAgI1xuICAgIGdldFJlYWxDdXJyZW50TGluZU5iOiAtPlxuICAgICAgICByZXR1cm4gQGN1cnJlbnRMaW5lTmIgKyBAb2Zmc2V0XG5cblxuICAgICMgUmV0dXJucyB0aGUgY3VycmVudCBsaW5lIGluZGVudGF0aW9uLlxuICAgICNcbiAgICAjIEByZXR1cm4gW0ludGVnZXJdICAgICBUaGUgY3VycmVudCBsaW5lIGluZGVudGF0aW9uXG4gICAgI1xuICAgIGdldEN1cnJlbnRMaW5lSW5kZW50YXRpb246IC0+XG4gICAgICAgIHJldHVybiBAY3VycmVudExpbmUubGVuZ3RoIC0gVXRpbHMubHRyaW0oQGN1cnJlbnRMaW5lLCAnICcpLmxlbmd0aFxuXG5cbiAgICAjIFJldHVybnMgdGhlIG5leHQgZW1iZWQgYmxvY2sgb2YgWUFNTC5cbiAgICAjXG4gICAgIyBAcGFyYW0gW0ludGVnZXJdICAgICAgICAgIGluZGVudGF0aW9uIFRoZSBpbmRlbnQgbGV2ZWwgYXQgd2hpY2ggdGhlIGJsb2NrIGlzIHRvIGJlIHJlYWQsIG9yIG51bGwgZm9yIGRlZmF1bHRcbiAgICAjXG4gICAgIyBAcmV0dXJuIFtTdHJpbmddICAgICAgICAgIEEgWUFNTCBzdHJpbmdcbiAgICAjXG4gICAgIyBAdGhyb3cgW1BhcnNlRXhjZXB0aW9uXSAgIFdoZW4gaW5kZW50YXRpb24gcHJvYmxlbSBhcmUgZGV0ZWN0ZWRcbiAgICAjXG4gICAgZ2V0TmV4dEVtYmVkQmxvY2s6IChpbmRlbnRhdGlvbiA9IG51bGwsIGluY2x1ZGVVbmluZGVudGVkQ29sbGVjdGlvbiA9IGZhbHNlKSAtPlxuICAgICAgICBAbW92ZVRvTmV4dExpbmUoKVxuXG4gICAgICAgIGlmIG5vdCBpbmRlbnRhdGlvbj9cbiAgICAgICAgICAgIG5ld0luZGVudCA9IEBnZXRDdXJyZW50TGluZUluZGVudGF0aW9uKClcblxuICAgICAgICAgICAgdW5pbmRlbnRlZEVtYmVkQmxvY2sgPSBAaXNTdHJpbmdVbkluZGVudGVkQ29sbGVjdGlvbkl0ZW0gQGN1cnJlbnRMaW5lXG5cbiAgICAgICAgICAgIGlmIG5vdChAaXNDdXJyZW50TGluZUVtcHR5KCkpIGFuZCAwIGlzIG5ld0luZGVudCBhbmQgbm90KHVuaW5kZW50ZWRFbWJlZEJsb2NrKVxuICAgICAgICAgICAgICAgIHRocm93IG5ldyBQYXJzZUV4Y2VwdGlvbiAnSW5kZW50YXRpb24gcHJvYmxlbS4nLCBAZ2V0UmVhbEN1cnJlbnRMaW5lTmIoKSArIDEsIEBjdXJyZW50TGluZVxuXG4gICAgICAgIGVsc2VcbiAgICAgICAgICAgIG5ld0luZGVudCA9IGluZGVudGF0aW9uXG5cblxuICAgICAgICBkYXRhID0gW0BjdXJyZW50TGluZVtuZXdJbmRlbnQuLl1dXG5cbiAgICAgICAgdW5sZXNzIGluY2x1ZGVVbmluZGVudGVkQ29sbGVjdGlvblxuICAgICAgICAgICAgaXNJdFVuaW5kZW50ZWRDb2xsZWN0aW9uID0gQGlzU3RyaW5nVW5JbmRlbnRlZENvbGxlY3Rpb25JdGVtIEBjdXJyZW50TGluZVxuXG4gICAgICAgICMgQ29tbWVudHMgbXVzdCBub3QgYmUgcmVtb3ZlZCBpbnNpZGUgYSBzdHJpbmcgYmxvY2sgKGllLiBhZnRlciBhIGxpbmUgZW5kaW5nIHdpdGggXCJ8XCIpXG4gICAgICAgICMgVGhleSBtdXN0IG5vdCBiZSByZW1vdmVkIGluc2lkZSBhIHN1Yi1lbWJlZGRlZCBibG9jayBhcyB3ZWxsXG4gICAgICAgIHJlbW92ZUNvbW1lbnRzUGF0dGVybiA9IEBQQVRURVJOX0ZPTERFRF9TQ0FMQVJfRU5EXG4gICAgICAgIHJlbW92ZUNvbW1lbnRzID0gbm90IHJlbW92ZUNvbW1lbnRzUGF0dGVybi50ZXN0IEBjdXJyZW50TGluZVxuXG4gICAgICAgIHdoaWxlIEBtb3ZlVG9OZXh0TGluZSgpXG4gICAgICAgICAgICBpbmRlbnQgPSBAZ2V0Q3VycmVudExpbmVJbmRlbnRhdGlvbigpXG5cbiAgICAgICAgICAgIGlmIGluZGVudCBpcyBuZXdJbmRlbnRcbiAgICAgICAgICAgICAgICByZW1vdmVDb21tZW50cyA9IG5vdCByZW1vdmVDb21tZW50c1BhdHRlcm4udGVzdCBAY3VycmVudExpbmVcblxuICAgICAgICAgICAgaWYgaXNJdFVuaW5kZW50ZWRDb2xsZWN0aW9uIGFuZCBub3QgQGlzU3RyaW5nVW5JbmRlbnRlZENvbGxlY3Rpb25JdGVtKEBjdXJyZW50TGluZSkgYW5kIGluZGVudCBpcyBuZXdJbmRlbnRcbiAgICAgICAgICAgICAgICBAbW92ZVRvUHJldmlvdXNMaW5lKClcbiAgICAgICAgICAgICAgICBicmVha1xuXG4gICAgICAgICAgICBpZiBAaXNDdXJyZW50TGluZUJsYW5rKClcbiAgICAgICAgICAgICAgICBkYXRhLnB1c2ggQGN1cnJlbnRMaW5lW25ld0luZGVudC4uXVxuICAgICAgICAgICAgICAgIGNvbnRpbnVlXG5cbiAgICAgICAgICAgIGlmIHJlbW92ZUNvbW1lbnRzIGFuZCBAaXNDdXJyZW50TGluZUNvbW1lbnQoKVxuICAgICAgICAgICAgICAgIGlmIGluZGVudCBpcyBuZXdJbmRlbnRcbiAgICAgICAgICAgICAgICAgICAgY29udGludWVcblxuICAgICAgICAgICAgaWYgaW5kZW50ID49IG5ld0luZGVudFxuICAgICAgICAgICAgICAgIGRhdGEucHVzaCBAY3VycmVudExpbmVbbmV3SW5kZW50Li5dXG4gICAgICAgICAgICBlbHNlIGlmIFV0aWxzLmx0cmltKEBjdXJyZW50TGluZSkuY2hhckF0KDApIGlzICcjJ1xuICAgICAgICAgICAgICAgICMgRG9uJ3QgYWRkIGxpbmUgd2l0aCBjb21tZW50c1xuICAgICAgICAgICAgZWxzZSBpZiAwIGlzIGluZGVudFxuICAgICAgICAgICAgICAgIEBtb3ZlVG9QcmV2aW91c0xpbmUoKVxuICAgICAgICAgICAgICAgIGJyZWFrXG4gICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IFBhcnNlRXhjZXB0aW9uICdJbmRlbnRhdGlvbiBwcm9ibGVtLicsIEBnZXRSZWFsQ3VycmVudExpbmVOYigpICsgMSwgQGN1cnJlbnRMaW5lXG5cblxuICAgICAgICByZXR1cm4gZGF0YS5qb2luIFwiXFxuXCJcblxuXG4gICAgIyBNb3ZlcyB0aGUgcGFyc2VyIHRvIHRoZSBuZXh0IGxpbmUuXG4gICAgI1xuICAgICMgQHJldHVybiBbQm9vbGVhbl1cbiAgICAjXG4gICAgbW92ZVRvTmV4dExpbmU6IC0+XG4gICAgICAgIGlmIEBjdXJyZW50TGluZU5iID49IEBsaW5lcy5sZW5ndGggLSAxXG4gICAgICAgICAgICByZXR1cm4gZmFsc2VcblxuICAgICAgICBAY3VycmVudExpbmUgPSBAbGluZXNbKytAY3VycmVudExpbmVOYl07XG5cbiAgICAgICAgcmV0dXJuIHRydWVcblxuXG4gICAgIyBNb3ZlcyB0aGUgcGFyc2VyIHRvIHRoZSBwcmV2aW91cyBsaW5lLlxuICAgICNcbiAgICBtb3ZlVG9QcmV2aW91c0xpbmU6IC0+XG4gICAgICAgIEBjdXJyZW50TGluZSA9IEBsaW5lc1stLUBjdXJyZW50TGluZU5iXVxuICAgICAgICByZXR1cm5cblxuXG4gICAgIyBQYXJzZXMgYSBZQU1MIHZhbHVlLlxuICAgICNcbiAgICAjIEBwYXJhbSBbU3RyaW5nXSAgIHZhbHVlICAgICAgICAgICAgICAgICAgIEEgWUFNTCB2YWx1ZVxuICAgICMgQHBhcmFtIFtCb29sZWFuXSAgZXhjZXB0aW9uT25JbnZhbGlkVHlwZSAgdHJ1ZSBpZiBhbiBleGNlcHRpb24gbXVzdCBiZSB0aHJvd24gb24gaW52YWxpZCB0eXBlcyBmYWxzZSBvdGhlcndpc2VcbiAgICAjIEBwYXJhbSBbRnVuY3Rpb25dIG9iamVjdERlY29kZXIgICAgICAgICAgIEEgZnVuY3Rpb24gdG8gZGVzZXJpYWxpemUgY3VzdG9tIG9iamVjdHMsIG51bGwgb3RoZXJ3aXNlXG4gICAgI1xuICAgICMgQHJldHVybiBbT2JqZWN0XSBBIEphdmFTY3JpcHQgdmFsdWVcbiAgICAjXG4gICAgIyBAdGhyb3cgW1BhcnNlRXhjZXB0aW9uXSBXaGVuIHJlZmVyZW5jZSBkb2VzIG5vdCBleGlzdFxuICAgICNcbiAgICBwYXJzZVZhbHVlOiAodmFsdWUsIGV4Y2VwdGlvbk9uSW52YWxpZFR5cGUsIG9iamVjdERlY29kZXIpIC0+XG4gICAgICAgIGlmIDAgaXMgdmFsdWUuaW5kZXhPZignKicpXG4gICAgICAgICAgICBwb3MgPSB2YWx1ZS5pbmRleE9mICcjJ1xuICAgICAgICAgICAgaWYgcG9zIGlzbnQgLTFcbiAgICAgICAgICAgICAgICB2YWx1ZSA9IHZhbHVlLnN1YnN0cigxLCBwb3MtMilcbiAgICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgICAgICB2YWx1ZSA9IHZhbHVlWzEuLl1cblxuICAgICAgICAgICAgaWYgQHJlZnNbdmFsdWVdIGlzIHVuZGVmaW5lZFxuICAgICAgICAgICAgICAgIHRocm93IG5ldyBQYXJzZUV4Y2VwdGlvbiAnUmVmZXJlbmNlIFwiJyt2YWx1ZSsnXCIgZG9lcyBub3QgZXhpc3QuJywgQGN1cnJlbnRMaW5lXG5cbiAgICAgICAgICAgIHJldHVybiBAcmVmc1t2YWx1ZV1cblxuXG4gICAgICAgIGlmIG1hdGNoZXMgPSBAUEFUVEVSTl9GT0xERURfU0NBTEFSX0FMTC5leGVjIHZhbHVlXG4gICAgICAgICAgICBtb2RpZmllcnMgPSBtYXRjaGVzLm1vZGlmaWVycyA/ICcnXG5cbiAgICAgICAgICAgIGZvbGRlZEluZGVudCA9IE1hdGguYWJzKHBhcnNlSW50KG1vZGlmaWVycykpXG4gICAgICAgICAgICBpZiBpc05hTihmb2xkZWRJbmRlbnQpIHRoZW4gZm9sZGVkSW5kZW50ID0gMFxuICAgICAgICAgICAgdmFsID0gQHBhcnNlRm9sZGVkU2NhbGFyIG1hdGNoZXMuc2VwYXJhdG9yLCBAUEFUVEVSTl9ERUNJTUFMLnJlcGxhY2UobW9kaWZpZXJzLCAnJyksIGZvbGRlZEluZGVudFxuICAgICAgICAgICAgaWYgbWF0Y2hlcy50eXBlP1xuICAgICAgICAgICAgICAgICMgRm9yY2UgY29ycmVjdCBzZXR0aW5nc1xuICAgICAgICAgICAgICAgIElubGluZS5jb25maWd1cmUgZXhjZXB0aW9uT25JbnZhbGlkVHlwZSwgb2JqZWN0RGVjb2RlclxuICAgICAgICAgICAgICAgIHJldHVybiBJbmxpbmUucGFyc2VTY2FsYXIgbWF0Y2hlcy50eXBlKycgJyt2YWxcbiAgICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgICAgICByZXR1cm4gdmFsXG5cbiAgICAgICAgdHJ5XG4gICAgICAgICAgICByZXR1cm4gSW5saW5lLnBhcnNlIHZhbHVlLCBleGNlcHRpb25PbkludmFsaWRUeXBlLCBvYmplY3REZWNvZGVyXG4gICAgICAgIGNhdGNoIGVcbiAgICAgICAgICAgICMgVHJ5IHRvIHBhcnNlIG11bHRpbGluZSBjb21wYWN0IHNlcXVlbmNlIG9yIG1hcHBpbmdcbiAgICAgICAgICAgIGlmIHZhbHVlLmNoYXJBdCgwKSBpbiBbJ1snLCAneyddIGFuZCBlIGluc3RhbmNlb2YgUGFyc2VFeGNlcHRpb24gYW5kIEBpc05leHRMaW5lSW5kZW50ZWQoKVxuICAgICAgICAgICAgICAgIHZhbHVlICs9IFwiXFxuXCIgKyBAZ2V0TmV4dEVtYmVkQmxvY2soKVxuICAgICAgICAgICAgICAgIHRyeVxuICAgICAgICAgICAgICAgICAgICByZXR1cm4gSW5saW5lLnBhcnNlIHZhbHVlLCBleGNlcHRpb25PbkludmFsaWRUeXBlLCBvYmplY3REZWNvZGVyXG4gICAgICAgICAgICAgICAgY2F0Y2ggZVxuICAgICAgICAgICAgICAgICAgICBlLnBhcnNlZExpbmUgPSBAZ2V0UmVhbEN1cnJlbnRMaW5lTmIoKSArIDFcbiAgICAgICAgICAgICAgICAgICAgZS5zbmlwcGV0ID0gQGN1cnJlbnRMaW5lXG5cbiAgICAgICAgICAgICAgICAgICAgdGhyb3cgZVxuXG4gICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAgZS5wYXJzZWRMaW5lID0gQGdldFJlYWxDdXJyZW50TGluZU5iKCkgKyAxXG4gICAgICAgICAgICAgICAgZS5zbmlwcGV0ID0gQGN1cnJlbnRMaW5lXG5cbiAgICAgICAgICAgICAgICB0aHJvdyBlXG5cbiAgICAgICAgcmV0dXJuXG5cblxuICAgICMgUGFyc2VzIGEgZm9sZGVkIHNjYWxhci5cbiAgICAjXG4gICAgIyBAcGFyYW0gW1N0cmluZ10gICAgICAgc2VwYXJhdG9yICAgVGhlIHNlcGFyYXRvciB0aGF0IHdhcyB1c2VkIHRvIGJlZ2luIHRoaXMgZm9sZGVkIHNjYWxhciAofCBvciA+KVxuICAgICMgQHBhcmFtIFtTdHJpbmddICAgICAgIGluZGljYXRvciAgIFRoZSBpbmRpY2F0b3IgdGhhdCB3YXMgdXNlZCB0byBiZWdpbiB0aGlzIGZvbGRlZCBzY2FsYXIgKCsgb3IgLSlcbiAgICAjIEBwYXJhbSBbSW50ZWdlcl0gICAgICBpbmRlbnRhdGlvbiBUaGUgaW5kZW50YXRpb24gdGhhdCB3YXMgdXNlZCB0byBiZWdpbiB0aGlzIGZvbGRlZCBzY2FsYXJcbiAgICAjXG4gICAgIyBAcmV0dXJuIFtTdHJpbmddICAgICAgVGhlIHRleHQgdmFsdWVcbiAgICAjXG4gICAgcGFyc2VGb2xkZWRTY2FsYXI6IChzZXBhcmF0b3IsIGluZGljYXRvciA9ICcnLCBpbmRlbnRhdGlvbiA9IDApIC0+XG4gICAgICAgIG5vdEVPRiA9IEBtb3ZlVG9OZXh0TGluZSgpXG4gICAgICAgIGlmIG5vdCBub3RFT0ZcbiAgICAgICAgICAgIHJldHVybiAnJ1xuXG4gICAgICAgIGlzQ3VycmVudExpbmVCbGFuayA9IEBpc0N1cnJlbnRMaW5lQmxhbmsoKVxuICAgICAgICB0ZXh0ID0gJydcblxuICAgICAgICAjIExlYWRpbmcgYmxhbmsgbGluZXMgYXJlIGNvbnN1bWVkIGJlZm9yZSBkZXRlcm1pbmluZyBpbmRlbnRhdGlvblxuICAgICAgICB3aGlsZSBub3RFT0YgYW5kIGlzQ3VycmVudExpbmVCbGFua1xuICAgICAgICAgICAgIyBuZXdsaW5lIG9ubHkgaWYgbm90IEVPRlxuICAgICAgICAgICAgaWYgbm90RU9GID0gQG1vdmVUb05leHRMaW5lKClcbiAgICAgICAgICAgICAgICB0ZXh0ICs9IFwiXFxuXCJcbiAgICAgICAgICAgICAgICBpc0N1cnJlbnRMaW5lQmxhbmsgPSBAaXNDdXJyZW50TGluZUJsYW5rKClcblxuXG4gICAgICAgICMgRGV0ZXJtaW5lIGluZGVudGF0aW9uIGlmIG5vdCBzcGVjaWZpZWRcbiAgICAgICAgaWYgMCBpcyBpbmRlbnRhdGlvblxuICAgICAgICAgICAgaWYgbWF0Y2hlcyA9IEBQQVRURVJOX0lOREVOVF9TUEFDRVMuZXhlYyBAY3VycmVudExpbmVcbiAgICAgICAgICAgICAgICBpbmRlbnRhdGlvbiA9IG1hdGNoZXNbMF0ubGVuZ3RoXG5cblxuICAgICAgICBpZiBpbmRlbnRhdGlvbiA+IDBcbiAgICAgICAgICAgIHBhdHRlcm4gPSBAUEFUVEVSTl9GT0xERURfU0NBTEFSX0JZX0lOREVOVEFUSU9OW2luZGVudGF0aW9uXVxuICAgICAgICAgICAgdW5sZXNzIHBhdHRlcm4/XG4gICAgICAgICAgICAgICAgcGF0dGVybiA9IG5ldyBQYXR0ZXJuICdeIHsnK2luZGVudGF0aW9uKyd9KC4qKSQnXG4gICAgICAgICAgICAgICAgUGFyc2VyOjpQQVRURVJOX0ZPTERFRF9TQ0FMQVJfQllfSU5ERU5UQVRJT05baW5kZW50YXRpb25dID0gcGF0dGVyblxuXG4gICAgICAgICAgICB3aGlsZSBub3RFT0YgYW5kIChpc0N1cnJlbnRMaW5lQmxhbmsgb3IgbWF0Y2hlcyA9IHBhdHRlcm4uZXhlYyBAY3VycmVudExpbmUpXG4gICAgICAgICAgICAgICAgaWYgaXNDdXJyZW50TGluZUJsYW5rXG4gICAgICAgICAgICAgICAgICAgIHRleHQgKz0gQGN1cnJlbnRMaW5lW2luZGVudGF0aW9uLi5dXG4gICAgICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAgICAgICAgICB0ZXh0ICs9IG1hdGNoZXNbMV1cblxuICAgICAgICAgICAgICAgICMgbmV3bGluZSBvbmx5IGlmIG5vdCBFT0ZcbiAgICAgICAgICAgICAgICBpZiBub3RFT0YgPSBAbW92ZVRvTmV4dExpbmUoKVxuICAgICAgICAgICAgICAgICAgICB0ZXh0ICs9IFwiXFxuXCJcbiAgICAgICAgICAgICAgICAgICAgaXNDdXJyZW50TGluZUJsYW5rID0gQGlzQ3VycmVudExpbmVCbGFuaygpXG5cbiAgICAgICAgZWxzZSBpZiBub3RFT0ZcbiAgICAgICAgICAgIHRleHQgKz0gXCJcXG5cIlxuXG5cbiAgICAgICAgaWYgbm90RU9GXG4gICAgICAgICAgICBAbW92ZVRvUHJldmlvdXNMaW5lKClcblxuXG4gICAgICAgICMgUmVtb3ZlIGxpbmUgYnJlYWtzIG9mIGVhY2ggbGluZXMgZXhjZXB0IHRoZSBlbXB0eSBhbmQgbW9yZSBpbmRlbnRlZCBvbmVzXG4gICAgICAgIGlmICc+JyBpcyBzZXBhcmF0b3JcbiAgICAgICAgICAgIG5ld1RleHQgPSAnJ1xuICAgICAgICAgICAgZm9yIGxpbmUgaW4gdGV4dC5zcGxpdCBcIlxcblwiXG4gICAgICAgICAgICAgICAgaWYgbGluZS5sZW5ndGggaXMgMCBvciBsaW5lLmNoYXJBdCgwKSBpcyAnICdcbiAgICAgICAgICAgICAgICAgICAgbmV3VGV4dCA9IFV0aWxzLnJ0cmltKG5ld1RleHQsICcgJykgKyBsaW5lICsgXCJcXG5cIlxuICAgICAgICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgICAgICAgICAgbmV3VGV4dCArPSBsaW5lICsgJyAnXG4gICAgICAgICAgICB0ZXh0ID0gbmV3VGV4dFxuXG4gICAgICAgIGlmICcrJyBpc250IGluZGljYXRvclxuICAgICAgICAgICAgIyBSZW1vdmUgYW55IGV4dHJhIHNwYWNlIG9yIG5ldyBsaW5lIGFzIHdlIGFyZSBhZGRpbmcgdGhlbSBhZnRlclxuICAgICAgICAgICAgdGV4dCA9IFV0aWxzLnJ0cmltKHRleHQpXG5cbiAgICAgICAgIyBEZWFsIHdpdGggdHJhaWxpbmcgbmV3bGluZXMgYXMgaW5kaWNhdGVkXG4gICAgICAgIGlmICcnIGlzIGluZGljYXRvclxuICAgICAgICAgICAgdGV4dCA9IEBQQVRURVJOX1RSQUlMSU5HX0xJTkVTLnJlcGxhY2UgdGV4dCwgXCJcXG5cIlxuICAgICAgICBlbHNlIGlmICctJyBpcyBpbmRpY2F0b3JcbiAgICAgICAgICAgIHRleHQgPSBAUEFUVEVSTl9UUkFJTElOR19MSU5FUy5yZXBsYWNlIHRleHQsICcnXG5cbiAgICAgICAgcmV0dXJuIHRleHRcblxuXG4gICAgIyBSZXR1cm5zIHRydWUgaWYgdGhlIG5leHQgbGluZSBpcyBpbmRlbnRlZC5cbiAgICAjXG4gICAgIyBAcmV0dXJuIFtCb29sZWFuXSAgICAgUmV0dXJucyB0cnVlIGlmIHRoZSBuZXh0IGxpbmUgaXMgaW5kZW50ZWQsIGZhbHNlIG90aGVyd2lzZVxuICAgICNcbiAgICBpc05leHRMaW5lSW5kZW50ZWQ6IChpZ25vcmVDb21tZW50cyA9IHRydWUpIC0+XG4gICAgICAgIGN1cnJlbnRJbmRlbnRhdGlvbiA9IEBnZXRDdXJyZW50TGluZUluZGVudGF0aW9uKClcbiAgICAgICAgRU9GID0gbm90IEBtb3ZlVG9OZXh0TGluZSgpXG5cbiAgICAgICAgaWYgaWdub3JlQ29tbWVudHNcbiAgICAgICAgICAgIHdoaWxlIG5vdChFT0YpIGFuZCBAaXNDdXJyZW50TGluZUVtcHR5KClcbiAgICAgICAgICAgICAgICBFT0YgPSBub3QgQG1vdmVUb05leHRMaW5lKClcbiAgICAgICAgZWxzZVxuICAgICAgICAgICAgd2hpbGUgbm90KEVPRikgYW5kIEBpc0N1cnJlbnRMaW5lQmxhbmsoKVxuICAgICAgICAgICAgICAgIEVPRiA9IG5vdCBAbW92ZVRvTmV4dExpbmUoKVxuXG4gICAgICAgIGlmIEVPRlxuICAgICAgICAgICAgcmV0dXJuIGZhbHNlXG5cbiAgICAgICAgcmV0ID0gZmFsc2VcbiAgICAgICAgaWYgQGdldEN1cnJlbnRMaW5lSW5kZW50YXRpb24oKSA+IGN1cnJlbnRJbmRlbnRhdGlvblxuICAgICAgICAgICAgcmV0ID0gdHJ1ZVxuXG4gICAgICAgIEBtb3ZlVG9QcmV2aW91c0xpbmUoKVxuXG4gICAgICAgIHJldHVybiByZXRcblxuXG4gICAgIyBSZXR1cm5zIHRydWUgaWYgdGhlIGN1cnJlbnQgbGluZSBpcyBibGFuayBvciBpZiBpdCBpcyBhIGNvbW1lbnQgbGluZS5cbiAgICAjXG4gICAgIyBAcmV0dXJuIFtCb29sZWFuXSAgICAgUmV0dXJucyB0cnVlIGlmIHRoZSBjdXJyZW50IGxpbmUgaXMgZW1wdHkgb3IgaWYgaXQgaXMgYSBjb21tZW50IGxpbmUsIGZhbHNlIG90aGVyd2lzZVxuICAgICNcbiAgICBpc0N1cnJlbnRMaW5lRW1wdHk6IC0+XG4gICAgICAgIHRyaW1tZWRMaW5lID0gVXRpbHMudHJpbShAY3VycmVudExpbmUsICcgJylcbiAgICAgICAgcmV0dXJuIHRyaW1tZWRMaW5lLmxlbmd0aCBpcyAwIG9yIHRyaW1tZWRMaW5lLmNoYXJBdCgwKSBpcyAnIydcblxuXG4gICAgIyBSZXR1cm5zIHRydWUgaWYgdGhlIGN1cnJlbnQgbGluZSBpcyBibGFuay5cbiAgICAjXG4gICAgIyBAcmV0dXJuIFtCb29sZWFuXSAgICAgUmV0dXJucyB0cnVlIGlmIHRoZSBjdXJyZW50IGxpbmUgaXMgYmxhbmssIGZhbHNlIG90aGVyd2lzZVxuICAgICNcbiAgICBpc0N1cnJlbnRMaW5lQmxhbms6IC0+XG4gICAgICAgIHJldHVybiAnJyBpcyBVdGlscy50cmltKEBjdXJyZW50TGluZSwgJyAnKVxuXG5cbiAgICAjIFJldHVybnMgdHJ1ZSBpZiB0aGUgY3VycmVudCBsaW5lIGlzIGEgY29tbWVudCBsaW5lLlxuICAgICNcbiAgICAjIEByZXR1cm4gW0Jvb2xlYW5dICAgICBSZXR1cm5zIHRydWUgaWYgdGhlIGN1cnJlbnQgbGluZSBpcyBhIGNvbW1lbnQgbGluZSwgZmFsc2Ugb3RoZXJ3aXNlXG4gICAgI1xuICAgIGlzQ3VycmVudExpbmVDb21tZW50OiAtPlxuICAgICAgICAjIENoZWNraW5nIGV4cGxpY2l0bHkgdGhlIGZpcnN0IGNoYXIgb2YgdGhlIHRyaW0gaXMgZmFzdGVyIHRoYW4gbG9vcHMgb3Igc3RycG9zXG4gICAgICAgIGx0cmltbWVkTGluZSA9IFV0aWxzLmx0cmltKEBjdXJyZW50TGluZSwgJyAnKVxuXG4gICAgICAgIHJldHVybiBsdHJpbW1lZExpbmUuY2hhckF0KDApIGlzICcjJ1xuXG5cbiAgICAjIENsZWFudXBzIGEgWUFNTCBzdHJpbmcgdG8gYmUgcGFyc2VkLlxuICAgICNcbiAgICAjIEBwYXJhbSBbU3RyaW5nXSAgIHZhbHVlIFRoZSBpbnB1dCBZQU1MIHN0cmluZ1xuICAgICNcbiAgICAjIEByZXR1cm4gW1N0cmluZ10gIEEgY2xlYW5lZCB1cCBZQU1MIHN0cmluZ1xuICAgICNcbiAgICBjbGVhbnVwOiAodmFsdWUpIC0+XG4gICAgICAgIGlmIHZhbHVlLmluZGV4T2YoXCJcXHJcIikgaXNudCAtMVxuICAgICAgICAgICAgdmFsdWUgPSB2YWx1ZS5zcGxpdChcIlxcclxcblwiKS5qb2luKFwiXFxuXCIpLnNwbGl0KFwiXFxyXCIpLmpvaW4oXCJcXG5cIilcblxuICAgICAgICAjIFN0cmlwIFlBTUwgaGVhZGVyXG4gICAgICAgIGNvdW50ID0gMFxuICAgICAgICBbdmFsdWUsIGNvdW50XSA9IEBQQVRURVJOX1lBTUxfSEVBREVSLnJlcGxhY2VBbGwgdmFsdWUsICcnXG4gICAgICAgIEBvZmZzZXQgKz0gY291bnRcblxuICAgICAgICAjIFJlbW92ZSBsZWFkaW5nIGNvbW1lbnRzXG4gICAgICAgIFt0cmltbWVkVmFsdWUsIGNvdW50XSA9IEBQQVRURVJOX0xFQURJTkdfQ09NTUVOVFMucmVwbGFjZUFsbCB2YWx1ZSwgJycsIDFcbiAgICAgICAgaWYgY291bnQgaXMgMVxuICAgICAgICAgICAgIyBJdGVtcyBoYXZlIGJlZW4gcmVtb3ZlZCwgdXBkYXRlIHRoZSBvZmZzZXRcbiAgICAgICAgICAgIEBvZmZzZXQgKz0gVXRpbHMuc3ViU3RyQ291bnQodmFsdWUsIFwiXFxuXCIpIC0gVXRpbHMuc3ViU3RyQ291bnQodHJpbW1lZFZhbHVlLCBcIlxcblwiKVxuICAgICAgICAgICAgdmFsdWUgPSB0cmltbWVkVmFsdWVcblxuICAgICAgICAjIFJlbW92ZSBzdGFydCBvZiB0aGUgZG9jdW1lbnQgbWFya2VyICgtLS0pXG4gICAgICAgIFt0cmltbWVkVmFsdWUsIGNvdW50XSA9IEBQQVRURVJOX0RPQ1VNRU5UX01BUktFUl9TVEFSVC5yZXBsYWNlQWxsIHZhbHVlLCAnJywgMVxuICAgICAgICBpZiBjb3VudCBpcyAxXG4gICAgICAgICAgICAjIEl0ZW1zIGhhdmUgYmVlbiByZW1vdmVkLCB1cGRhdGUgdGhlIG9mZnNldFxuICAgICAgICAgICAgQG9mZnNldCArPSBVdGlscy5zdWJTdHJDb3VudCh2YWx1ZSwgXCJcXG5cIikgLSBVdGlscy5zdWJTdHJDb3VudCh0cmltbWVkVmFsdWUsIFwiXFxuXCIpXG4gICAgICAgICAgICB2YWx1ZSA9IHRyaW1tZWRWYWx1ZVxuXG4gICAgICAgICAgICAjIFJlbW92ZSBlbmQgb2YgdGhlIGRvY3VtZW50IG1hcmtlciAoLi4uKVxuICAgICAgICAgICAgdmFsdWUgPSBAUEFUVEVSTl9ET0NVTUVOVF9NQVJLRVJfRU5ELnJlcGxhY2UgdmFsdWUsICcnXG5cbiAgICAgICAgIyBFbnN1cmUgdGhlIGJsb2NrIGlzIG5vdCBpbmRlbnRlZFxuICAgICAgICBsaW5lcyA9IHZhbHVlLnNwbGl0KFwiXFxuXCIpXG4gICAgICAgIHNtYWxsZXN0SW5kZW50ID0gLTFcbiAgICAgICAgZm9yIGxpbmUgaW4gbGluZXNcbiAgICAgICAgICAgIGNvbnRpbnVlIGlmIFV0aWxzLnRyaW0obGluZSwgJyAnKS5sZW5ndGggPT0gMFxuICAgICAgICAgICAgaW5kZW50ID0gbGluZS5sZW5ndGggLSBVdGlscy5sdHJpbShsaW5lKS5sZW5ndGhcbiAgICAgICAgICAgIGlmIHNtYWxsZXN0SW5kZW50IGlzIC0xIG9yIGluZGVudCA8IHNtYWxsZXN0SW5kZW50XG4gICAgICAgICAgICAgICAgc21hbGxlc3RJbmRlbnQgPSBpbmRlbnRcbiAgICAgICAgaWYgc21hbGxlc3RJbmRlbnQgPiAwXG4gICAgICAgICAgICBmb3IgbGluZSwgaSBpbiBsaW5lc1xuICAgICAgICAgICAgICAgIGxpbmVzW2ldID0gbGluZVtzbWFsbGVzdEluZGVudC4uXVxuICAgICAgICAgICAgdmFsdWUgPSBsaW5lcy5qb2luKFwiXFxuXCIpXG5cbiAgICAgICAgcmV0dXJuIHZhbHVlXG5cblxuICAgICMgUmV0dXJucyB0cnVlIGlmIHRoZSBuZXh0IGxpbmUgc3RhcnRzIHVuaW5kZW50ZWQgY29sbGVjdGlvblxuICAgICNcbiAgICAjIEByZXR1cm4gW0Jvb2xlYW5dICAgICBSZXR1cm5zIHRydWUgaWYgdGhlIG5leHQgbGluZSBzdGFydHMgdW5pbmRlbnRlZCBjb2xsZWN0aW9uLCBmYWxzZSBvdGhlcndpc2VcbiAgICAjXG4gICAgaXNOZXh0TGluZVVuSW5kZW50ZWRDb2xsZWN0aW9uOiAoY3VycmVudEluZGVudGF0aW9uID0gbnVsbCkgLT5cbiAgICAgICAgY3VycmVudEluZGVudGF0aW9uID89IEBnZXRDdXJyZW50TGluZUluZGVudGF0aW9uKClcbiAgICAgICAgbm90RU9GID0gQG1vdmVUb05leHRMaW5lKClcblxuICAgICAgICB3aGlsZSBub3RFT0YgYW5kIEBpc0N1cnJlbnRMaW5lRW1wdHkoKVxuICAgICAgICAgICAgbm90RU9GID0gQG1vdmVUb05leHRMaW5lKClcblxuICAgICAgICBpZiBmYWxzZSBpcyBub3RFT0ZcbiAgICAgICAgICAgIHJldHVybiBmYWxzZVxuXG4gICAgICAgIHJldCA9IGZhbHNlXG4gICAgICAgIGlmIEBnZXRDdXJyZW50TGluZUluZGVudGF0aW9uKCkgaXMgY3VycmVudEluZGVudGF0aW9uIGFuZCBAaXNTdHJpbmdVbkluZGVudGVkQ29sbGVjdGlvbkl0ZW0oQGN1cnJlbnRMaW5lKVxuICAgICAgICAgICAgcmV0ID0gdHJ1ZVxuXG4gICAgICAgIEBtb3ZlVG9QcmV2aW91c0xpbmUoKVxuXG4gICAgICAgIHJldHVybiByZXRcblxuXG4gICAgIyBSZXR1cm5zIHRydWUgaWYgdGhlIHN0cmluZyBpcyB1bi1pbmRlbnRlZCBjb2xsZWN0aW9uIGl0ZW1cbiAgICAjXG4gICAgIyBAcmV0dXJuIFtCb29sZWFuXSAgICAgUmV0dXJucyB0cnVlIGlmIHRoZSBzdHJpbmcgaXMgdW4taW5kZW50ZWQgY29sbGVjdGlvbiBpdGVtLCBmYWxzZSBvdGhlcndpc2VcbiAgICAjXG4gICAgaXNTdHJpbmdVbkluZGVudGVkQ29sbGVjdGlvbkl0ZW06IC0+XG4gICAgICAgIHJldHVybiBAY3VycmVudExpbmUgaXMgJy0nIG9yIEBjdXJyZW50TGluZVswLi4uMl0gaXMgJy0gJ1xuXG5cbm1vZHVsZS5leHBvcnRzID0gUGFyc2VyXG4iLCJcbiMgUGF0dGVybiBpcyBhIHplcm8tY29uZmxpY3Qgd3JhcHBlciBleHRlbmRpbmcgUmVnRXhwIGZlYXR1cmVzXG4jIGluIG9yZGVyIHRvIG1ha2UgWUFNTCBwYXJzaW5nIHJlZ2V4IG1vcmUgZXhwcmVzc2l2ZS5cbiNcbmNsYXNzIFBhdHRlcm5cblxuICAgICMgQHByb3BlcnR5IFtSZWdFeHBdIFRoZSBSZWdFeHAgaW5zdGFuY2VcbiAgICByZWdleDogICAgICAgICAgbnVsbFxuXG4gICAgIyBAcHJvcGVydHkgW1N0cmluZ10gVGhlIHJhdyByZWdleCBzdHJpbmdcbiAgICByYXdSZWdleDogICAgICAgbnVsbFxuXG4gICAgIyBAcHJvcGVydHkgW1N0cmluZ10gVGhlIGNsZWFuZWQgcmVnZXggc3RyaW5nICh1c2VkIHRvIGNyZWF0ZSB0aGUgUmVnRXhwIGluc3RhbmNlKVxuICAgIGNsZWFuZWRSZWdleDogICBudWxsXG5cbiAgICAjIEBwcm9wZXJ0eSBbT2JqZWN0XSBUaGUgZGljdGlvbmFyeSBtYXBwaW5nIG5hbWVzIHRvIGNhcHR1cmluZyBicmFja2V0IG51bWJlcnNcbiAgICBtYXBwaW5nOiAgICAgICAgbnVsbFxuXG4gICAgIyBDb25zdHJ1Y3RvclxuICAgICNcbiAgICAjIEBwYXJhbSBbU3RyaW5nXSByYXdSZWdleCBUaGUgcmF3IHJlZ2V4IHN0cmluZyBkZWZpbmluZyB0aGUgcGF0dGVyblxuICAgICNcbiAgICBjb25zdHJ1Y3RvcjogKHJhd1JlZ2V4LCBtb2RpZmllcnMgPSAnJykgLT5cbiAgICAgICAgY2xlYW5lZFJlZ2V4ID0gJydcbiAgICAgICAgbGVuID0gcmF3UmVnZXgubGVuZ3RoXG4gICAgICAgIG1hcHBpbmcgPSBudWxsXG5cbiAgICAgICAgIyBDbGVhbnVwIHJhdyByZWdleCBhbmQgY29tcHV0ZSBtYXBwaW5nXG4gICAgICAgIGNhcHR1cmluZ0JyYWNrZXROdW1iZXIgPSAwXG4gICAgICAgIGkgPSAwXG4gICAgICAgIHdoaWxlIGkgPCBsZW5cbiAgICAgICAgICAgIF9jaGFyID0gcmF3UmVnZXguY2hhckF0KGkpXG4gICAgICAgICAgICBpZiBfY2hhciBpcyAnXFxcXCdcbiAgICAgICAgICAgICAgICAjIElnbm9yZSBuZXh0IGNoYXJhY3RlclxuICAgICAgICAgICAgICAgIGNsZWFuZWRSZWdleCArPSByYXdSZWdleFtpLi5pKzFdXG4gICAgICAgICAgICAgICAgaSsrXG4gICAgICAgICAgICBlbHNlIGlmIF9jaGFyIGlzICcoJ1xuICAgICAgICAgICAgICAgICMgSW5jcmVhc2UgYnJhY2tldCBudW1iZXIsIG9ubHkgaWYgaXQgaXMgY2FwdHVyaW5nXG4gICAgICAgICAgICAgICAgaWYgaSA8IGxlbiAtIDJcbiAgICAgICAgICAgICAgICAgICAgcGFydCA9IHJhd1JlZ2V4W2kuLmkrMl1cbiAgICAgICAgICAgICAgICAgICAgaWYgcGFydCBpcyAnKD86J1xuICAgICAgICAgICAgICAgICAgICAgICAgIyBOb24tY2FwdHVyaW5nIGJyYWNrZXRcbiAgICAgICAgICAgICAgICAgICAgICAgIGkgKz0gMlxuICAgICAgICAgICAgICAgICAgICAgICAgY2xlYW5lZFJlZ2V4ICs9IHBhcnRcbiAgICAgICAgICAgICAgICAgICAgZWxzZSBpZiBwYXJ0IGlzICcoPzwnXG4gICAgICAgICAgICAgICAgICAgICAgICAjIENhcHR1cmluZyBicmFja2V0IHdpdGggcG9zc2libHkgYSBuYW1lXG4gICAgICAgICAgICAgICAgICAgICAgICBjYXB0dXJpbmdCcmFja2V0TnVtYmVyKytcbiAgICAgICAgICAgICAgICAgICAgICAgIGkgKz0gMlxuICAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICcnXG4gICAgICAgICAgICAgICAgICAgICAgICB3aGlsZSBpICsgMSA8IGxlblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1YkNoYXIgPSByYXdSZWdleC5jaGFyQXQoaSArIDEpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgc3ViQ2hhciBpcyAnPidcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xlYW5lZFJlZ2V4ICs9ICcoJ1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpKytcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgbmFtZS5sZW5ndGggPiAwXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEFzc29jaWF0ZSBhIG5hbWUgd2l0aCBhIGNhcHR1cmluZyBicmFja2V0IG51bWJlclxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFwcGluZyA/PSB7fVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFwcGluZ1tuYW1lXSA9IGNhcHR1cmluZ0JyYWNrZXROdW1iZXJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgKz0gc3ViQ2hhclxuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgaSsrXG4gICAgICAgICAgICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgICAgICAgICAgICAgIGNsZWFuZWRSZWdleCArPSBfY2hhclxuICAgICAgICAgICAgICAgICAgICAgICAgY2FwdHVyaW5nQnJhY2tldE51bWJlcisrXG4gICAgICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAgICAgICAgICBjbGVhbmVkUmVnZXggKz0gX2NoYXJcbiAgICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgICAgICBjbGVhbmVkUmVnZXggKz0gX2NoYXJcblxuICAgICAgICAgICAgaSsrXG5cbiAgICAgICAgQHJhd1JlZ2V4ID0gcmF3UmVnZXhcbiAgICAgICAgQGNsZWFuZWRSZWdleCA9IGNsZWFuZWRSZWdleFxuICAgICAgICBAcmVnZXggPSBuZXcgUmVnRXhwIEBjbGVhbmVkUmVnZXgsICdnJyttb2RpZmllcnMucmVwbGFjZSgnZycsICcnKVxuICAgICAgICBAbWFwcGluZyA9IG1hcHBpbmdcblxuXG4gICAgIyBFeGVjdXRlcyB0aGUgcGF0dGVybidzIHJlZ2V4IGFuZCByZXR1cm5zIHRoZSBtYXRjaGluZyB2YWx1ZXNcbiAgICAjXG4gICAgIyBAcGFyYW0gW1N0cmluZ10gc3RyIFRoZSBzdHJpbmcgdG8gdXNlIHRvIGV4ZWN1dGUgdGhlIHBhdHRlcm5cbiAgICAjXG4gICAgIyBAcmV0dXJuIFtBcnJheV0gVGhlIG1hdGNoaW5nIHZhbHVlcyBleHRyYWN0ZWQgZnJvbSBjYXB0dXJpbmcgYnJhY2tldHMgb3IgbnVsbCBpZiBub3RoaW5nIG1hdGNoZWRcbiAgICAjXG4gICAgZXhlYzogKHN0cikgLT5cbiAgICAgICAgQHJlZ2V4Lmxhc3RJbmRleCA9IDBcbiAgICAgICAgbWF0Y2hlcyA9IEByZWdleC5leGVjIHN0clxuXG4gICAgICAgIGlmIG5vdCBtYXRjaGVzP1xuICAgICAgICAgICAgcmV0dXJuIG51bGxcblxuICAgICAgICBpZiBAbWFwcGluZz9cbiAgICAgICAgICAgIGZvciBuYW1lLCBpbmRleCBvZiBAbWFwcGluZ1xuICAgICAgICAgICAgICAgIG1hdGNoZXNbbmFtZV0gPSBtYXRjaGVzW2luZGV4XVxuXG4gICAgICAgIHJldHVybiBtYXRjaGVzXG5cblxuICAgICMgVGVzdHMgdGhlIHBhdHRlcm4ncyByZWdleFxuICAgICNcbiAgICAjIEBwYXJhbSBbU3RyaW5nXSBzdHIgVGhlIHN0cmluZyB0byB1c2UgdG8gdGVzdCB0aGUgcGF0dGVyblxuICAgICNcbiAgICAjIEByZXR1cm4gW0Jvb2xlYW5dIHRydWUgaWYgdGhlIHN0cmluZyBtYXRjaGVkXG4gICAgI1xuICAgIHRlc3Q6IChzdHIpIC0+XG4gICAgICAgIEByZWdleC5sYXN0SW5kZXggPSAwXG4gICAgICAgIHJldHVybiBAcmVnZXgudGVzdCBzdHJcblxuXG4gICAgIyBSZXBsYWNlcyBvY2N1cmVuY2VzIG1hdGNoaW5nIHdpdGggdGhlIHBhdHRlcm4ncyByZWdleCB3aXRoIHJlcGxhY2VtZW50XG4gICAgI1xuICAgICMgQHBhcmFtIFtTdHJpbmddIHN0ciBUaGUgc291cmNlIHN0cmluZyB0byBwZXJmb3JtIHJlcGxhY2VtZW50c1xuICAgICMgQHBhcmFtIFtTdHJpbmddIHJlcGxhY2VtZW50IFRoZSBzdHJpbmcgdG8gdXNlIGluIHBsYWNlIG9mIGVhY2ggcmVwbGFjZWQgb2NjdXJlbmNlLlxuICAgICNcbiAgICAjIEByZXR1cm4gW1N0cmluZ10gVGhlIHJlcGxhY2VkIHN0cmluZ1xuICAgICNcbiAgICByZXBsYWNlOiAoc3RyLCByZXBsYWNlbWVudCkgLT5cbiAgICAgICAgQHJlZ2V4Lmxhc3RJbmRleCA9IDBcbiAgICAgICAgcmV0dXJuIHN0ci5yZXBsYWNlIEByZWdleCwgcmVwbGFjZW1lbnRcblxuXG4gICAgIyBSZXBsYWNlcyBvY2N1cmVuY2VzIG1hdGNoaW5nIHdpdGggdGhlIHBhdHRlcm4ncyByZWdleCB3aXRoIHJlcGxhY2VtZW50IGFuZFxuICAgICMgZ2V0IGJvdGggdGhlIHJlcGxhY2VkIHN0cmluZyBhbmQgdGhlIG51bWJlciBvZiByZXBsYWNlZCBvY2N1cmVuY2VzIGluIHRoZSBzdHJpbmcuXG4gICAgI1xuICAgICMgQHBhcmFtIFtTdHJpbmddIHN0ciBUaGUgc291cmNlIHN0cmluZyB0byBwZXJmb3JtIHJlcGxhY2VtZW50c1xuICAgICMgQHBhcmFtIFtTdHJpbmddIHJlcGxhY2VtZW50IFRoZSBzdHJpbmcgdG8gdXNlIGluIHBsYWNlIG9mIGVhY2ggcmVwbGFjZWQgb2NjdXJlbmNlLlxuICAgICMgQHBhcmFtIFtJbnRlZ2VyXSBsaW1pdCBUaGUgbWF4aW11bSBudW1iZXIgb2Ygb2NjdXJlbmNlcyB0byByZXBsYWNlICgwIG1lYW5zIGluZmluaXRlIG51bWJlciBvZiBvY2N1cmVuY2VzKVxuICAgICNcbiAgICAjIEByZXR1cm4gW0FycmF5XSBBIGRlc3RydWN0dXJhYmxlIGFycmF5IGNvbnRhaW5pbmcgdGhlIHJlcGxhY2VkIHN0cmluZyBhbmQgdGhlIG51bWJlciBvZiByZXBsYWNlZCBvY2N1cmVuY2VzLiBGb3IgaW5zdGFuY2U6IFtcIm15IHJlcGxhY2VkIHN0cmluZ1wiLCAyXVxuICAgICNcbiAgICByZXBsYWNlQWxsOiAoc3RyLCByZXBsYWNlbWVudCwgbGltaXQgPSAwKSAtPlxuICAgICAgICBAcmVnZXgubGFzdEluZGV4ID0gMFxuICAgICAgICBjb3VudCA9IDBcbiAgICAgICAgd2hpbGUgQHJlZ2V4LnRlc3Qoc3RyKSBhbmQgKGxpbWl0IGlzIDAgb3IgY291bnQgPCBsaW1pdClcbiAgICAgICAgICAgIEByZWdleC5sYXN0SW5kZXggPSAwXG4gICAgICAgICAgICBzdHIgPSBzdHIucmVwbGFjZSBAcmVnZXgsICcnXG4gICAgICAgICAgICBjb3VudCsrXG4gICAgICAgIFxuICAgICAgICByZXR1cm4gW3N0ciwgY291bnRdXG5cblxubW9kdWxlLmV4cG9ydHMgPSBQYXR0ZXJuXG5cbiIsIlxuVXRpbHMgICA9IHJlcXVpcmUgJy4vVXRpbHMnXG5QYXR0ZXJuID0gcmVxdWlyZSAnLi9QYXR0ZXJuJ1xuXG4jIFVuZXNjYXBlciBlbmNhcHN1bGF0ZXMgdW5lc2NhcGluZyBydWxlcyBmb3Igc2luZ2xlIGFuZCBkb3VibGUtcXVvdGVkIFlBTUwgc3RyaW5ncy5cbiNcbmNsYXNzIFVuZXNjYXBlclxuXG4gICAgIyBSZWdleCBmcmFnbWVudCB0aGF0IG1hdGNoZXMgYW4gZXNjYXBlZCBjaGFyYWN0ZXIgaW5cbiAgICAjIGEgZG91YmxlIHF1b3RlZCBzdHJpbmcuXG4gICAgQFBBVFRFUk5fRVNDQVBFRF9DSEFSQUNURVI6ICAgICBuZXcgUGF0dGVybiAnXFxcXFxcXFwoWzBhYnRcXHRudmZyZSBcIlxcXFwvXFxcXFxcXFxOX0xQXXx4WzAtOWEtZkEtRl17Mn18dVswLTlhLWZBLUZdezR9fFVbMC05YS1mQS1GXXs4fSknO1xuXG5cbiAgICAjIFVuZXNjYXBlcyBhIHNpbmdsZSBxdW90ZWQgc3RyaW5nLlxuICAgICNcbiAgICAjIEBwYXJhbSBbU3RyaW5nXSAgICAgICB2YWx1ZSBBIHNpbmdsZSBxdW90ZWQgc3RyaW5nLlxuICAgICNcbiAgICAjIEByZXR1cm4gW1N0cmluZ10gICAgICBUaGUgdW5lc2NhcGVkIHN0cmluZy5cbiAgICAjXG4gICAgQHVuZXNjYXBlU2luZ2xlUXVvdGVkU3RyaW5nOiAodmFsdWUpIC0+XG4gICAgICAgIHJldHVybiB2YWx1ZS5yZXBsYWNlKC9cXCdcXCcvZywgJ1xcJycpXG5cblxuICAgICMgVW5lc2NhcGVzIGEgZG91YmxlIHF1b3RlZCBzdHJpbmcuXG4gICAgI1xuICAgICMgQHBhcmFtIFtTdHJpbmddICAgICAgIHZhbHVlIEEgZG91YmxlIHF1b3RlZCBzdHJpbmcuXG4gICAgI1xuICAgICMgQHJldHVybiBbU3RyaW5nXSAgICAgIFRoZSB1bmVzY2FwZWQgc3RyaW5nLlxuICAgICNcbiAgICBAdW5lc2NhcGVEb3VibGVRdW90ZWRTdHJpbmc6ICh2YWx1ZSkgLT5cbiAgICAgICAgQF91bmVzY2FwZUNhbGxiYWNrID89IChzdHIpID0+XG4gICAgICAgICAgICByZXR1cm4gQHVuZXNjYXBlQ2hhcmFjdGVyKHN0cilcblxuICAgICAgICAjIEV2YWx1YXRlIHRoZSBzdHJpbmdcbiAgICAgICAgcmV0dXJuIEBQQVRURVJOX0VTQ0FQRURfQ0hBUkFDVEVSLnJlcGxhY2UgdmFsdWUsIEBfdW5lc2NhcGVDYWxsYmFja1xuXG5cbiAgICAjIFVuZXNjYXBlcyBhIGNoYXJhY3RlciB0aGF0IHdhcyBmb3VuZCBpbiBhIGRvdWJsZS1xdW90ZWQgc3RyaW5nXG4gICAgI1xuICAgICMgQHBhcmFtIFtTdHJpbmddICAgICAgIHZhbHVlIEFuIGVzY2FwZWQgY2hhcmFjdGVyXG4gICAgI1xuICAgICMgQHJldHVybiBbU3RyaW5nXSAgICAgIFRoZSB1bmVzY2FwZWQgY2hhcmFjdGVyXG4gICAgI1xuICAgIEB1bmVzY2FwZUNoYXJhY3RlcjogKHZhbHVlKSAtPlxuICAgICAgICBjaCA9IFN0cmluZy5mcm9tQ2hhckNvZGVcbiAgICAgICAgc3dpdGNoIHZhbHVlLmNoYXJBdCgxKVxuICAgICAgICAgICAgd2hlbiAnMCdcbiAgICAgICAgICAgICAgICByZXR1cm4gY2goMClcbiAgICAgICAgICAgIHdoZW4gJ2EnXG4gICAgICAgICAgICAgICAgcmV0dXJuIGNoKDcpXG4gICAgICAgICAgICB3aGVuICdiJ1xuICAgICAgICAgICAgICAgIHJldHVybiBjaCg4KVxuICAgICAgICAgICAgd2hlbiAndCdcbiAgICAgICAgICAgICAgICByZXR1cm4gXCJcXHRcIlxuICAgICAgICAgICAgd2hlbiBcIlxcdFwiXG4gICAgICAgICAgICAgICAgcmV0dXJuIFwiXFx0XCJcbiAgICAgICAgICAgIHdoZW4gJ24nXG4gICAgICAgICAgICAgICAgcmV0dXJuIFwiXFxuXCJcbiAgICAgICAgICAgIHdoZW4gJ3YnXG4gICAgICAgICAgICAgICAgcmV0dXJuIGNoKDExKVxuICAgICAgICAgICAgd2hlbiAnZidcbiAgICAgICAgICAgICAgICByZXR1cm4gY2goMTIpXG4gICAgICAgICAgICB3aGVuICdyJ1xuICAgICAgICAgICAgICAgIHJldHVybiBjaCgxMylcbiAgICAgICAgICAgIHdoZW4gJ2UnXG4gICAgICAgICAgICAgICAgcmV0dXJuIGNoKDI3KVxuICAgICAgICAgICAgd2hlbiAnICdcbiAgICAgICAgICAgICAgICByZXR1cm4gJyAnXG4gICAgICAgICAgICB3aGVuICdcIidcbiAgICAgICAgICAgICAgICByZXR1cm4gJ1wiJ1xuICAgICAgICAgICAgd2hlbiAnLydcbiAgICAgICAgICAgICAgICByZXR1cm4gJy8nXG4gICAgICAgICAgICB3aGVuICdcXFxcJ1xuICAgICAgICAgICAgICAgIHJldHVybiAnXFxcXCdcbiAgICAgICAgICAgIHdoZW4gJ04nXG4gICAgICAgICAgICAgICAgIyBVKzAwODUgTkVYVCBMSU5FXG4gICAgICAgICAgICAgICAgcmV0dXJuIGNoKDB4MDA4NSlcbiAgICAgICAgICAgIHdoZW4gJ18nXG4gICAgICAgICAgICAgICAgIyBVKzAwQTAgTk8tQlJFQUsgU1BBQ0VcbiAgICAgICAgICAgICAgICByZXR1cm4gY2goMHgwMEEwKVxuICAgICAgICAgICAgd2hlbiAnTCdcbiAgICAgICAgICAgICAgICAjIFUrMjAyOCBMSU5FIFNFUEFSQVRPUlxuICAgICAgICAgICAgICAgIHJldHVybiBjaCgweDIwMjgpXG4gICAgICAgICAgICB3aGVuICdQJ1xuICAgICAgICAgICAgICAgICMgVSsyMDI5IFBBUkFHUkFQSCBTRVBBUkFUT1JcbiAgICAgICAgICAgICAgICByZXR1cm4gY2goMHgyMDI5KVxuICAgICAgICAgICAgd2hlbiAneCdcbiAgICAgICAgICAgICAgICByZXR1cm4gVXRpbHMudXRmOGNocihVdGlscy5oZXhEZWModmFsdWUuc3Vic3RyKDIsIDIpKSlcbiAgICAgICAgICAgIHdoZW4gJ3UnXG4gICAgICAgICAgICAgICAgcmV0dXJuIFV0aWxzLnV0ZjhjaHIoVXRpbHMuaGV4RGVjKHZhbHVlLnN1YnN0cigyLCA0KSkpXG4gICAgICAgICAgICB3aGVuICdVJ1xuICAgICAgICAgICAgICAgIHJldHVybiBVdGlscy51dGY4Y2hyKFV0aWxzLmhleERlYyh2YWx1ZS5zdWJzdHIoMiwgOCkpKVxuICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAgICAgIHJldHVybiAnJ1xuXG5tb2R1bGUuZXhwb3J0cyA9IFVuZXNjYXBlclxuIiwiXG5QYXR0ZXJuID0gcmVxdWlyZSAnLi9QYXR0ZXJuJ1xuXG4jIEEgYnVuY2ggb2YgdXRpbGl0eSBtZXRob2RzXG4jXG5jbGFzcyBVdGlsc1xuXG4gICAgQFJFR0VYX0xFRlRfVFJJTV9CWV9DSEFSOiAgIHt9XG4gICAgQFJFR0VYX1JJR0hUX1RSSU1fQllfQ0hBUjogIHt9XG4gICAgQFJFR0VYX1NQQUNFUzogICAgICAgICAgICAgIC9cXHMrL2dcbiAgICBAUkVHRVhfRElHSVRTOiAgICAgICAgICAgICAgL15cXGQrJC9cbiAgICBAUkVHRVhfT0NUQUw6ICAgICAgICAgICAgICAgL1teMC03XS9naVxuICAgIEBSRUdFWF9IRVhBREVDSU1BTDogICAgICAgICAvW15hLWYwLTldL2dpXG5cbiAgICAjIFByZWNvbXBpbGVkIGRhdGUgcGF0dGVyblxuICAgIEBQQVRURVJOX0RBVEU6ICAgICAgICAgICAgICBuZXcgUGF0dGVybiAnXicrXG4gICAgICAgICAgICAnKD88eWVhcj5bMC05XVswLTldWzAtOV1bMC05XSknK1xuICAgICAgICAgICAgJy0oPzxtb250aD5bMC05XVswLTldPyknK1xuICAgICAgICAgICAgJy0oPzxkYXk+WzAtOV1bMC05XT8pJytcbiAgICAgICAgICAgICcoPzooPzpbVHRdfFsgXFx0XSspJytcbiAgICAgICAgICAgICcoPzxob3VyPlswLTldWzAtOV0/KScrXG4gICAgICAgICAgICAnOig/PG1pbnV0ZT5bMC05XVswLTldKScrXG4gICAgICAgICAgICAnOig/PHNlY29uZD5bMC05XVswLTldKScrXG4gICAgICAgICAgICAnKD86XFwuKD88ZnJhY3Rpb24+WzAtOV0qKSk/JytcbiAgICAgICAgICAgICcoPzpbIFxcdF0qKD88dHo+WnwoPzx0el9zaWduPlstK10pKD88dHpfaG91cj5bMC05XVswLTldPyknK1xuICAgICAgICAgICAgJyg/OjooPzx0el9taW51dGU+WzAtOV1bMC05XSkpPykpPyk/JytcbiAgICAgICAgICAgICckJywgJ2knXG5cbiAgICAjIExvY2FsIHRpbWV6b25lIG9mZnNldCBpbiBtc1xuICAgIEBMT0NBTF9USU1FWk9ORV9PRkZTRVQ6ICAgICBuZXcgRGF0ZSgpLmdldFRpbWV6b25lT2Zmc2V0KCkgKiA2MCAqIDEwMDBcblxuICAgICMgVHJpbXMgdGhlIGdpdmVuIHN0cmluZyBvbiBib3RoIHNpZGVzXG4gICAgI1xuICAgICMgQHBhcmFtIFtTdHJpbmddIHN0ciBUaGUgc3RyaW5nIHRvIHRyaW1cbiAgICAjIEBwYXJhbSBbU3RyaW5nXSBfY2hhciBUaGUgY2hhcmFjdGVyIHRvIHVzZSBmb3IgdHJpbW1pbmcgKGRlZmF1bHQ6ICdcXFxccycpXG4gICAgI1xuICAgICMgQHJldHVybiBbU3RyaW5nXSBBIHRyaW1tZWQgc3RyaW5nXG4gICAgI1xuICAgIEB0cmltOiAoc3RyLCBfY2hhciA9ICdcXFxccycpIC0+XG4gICAgICAgIHJldHVybiBzdHIudHJpbSgpXG4gICAgICAgIHJlZ2V4TGVmdCA9IEBSRUdFWF9MRUZUX1RSSU1fQllfQ0hBUltfY2hhcl1cbiAgICAgICAgdW5sZXNzIHJlZ2V4TGVmdD9cbiAgICAgICAgICAgIEBSRUdFWF9MRUZUX1RSSU1fQllfQ0hBUltfY2hhcl0gPSByZWdleExlZnQgPSBuZXcgUmVnRXhwICdeJytfY2hhcisnJytfY2hhcisnKidcbiAgICAgICAgcmVnZXhMZWZ0Lmxhc3RJbmRleCA9IDBcbiAgICAgICAgcmVnZXhSaWdodCA9IEBSRUdFWF9SSUdIVF9UUklNX0JZX0NIQVJbX2NoYXJdXG4gICAgICAgIHVubGVzcyByZWdleFJpZ2h0P1xuICAgICAgICAgICAgQFJFR0VYX1JJR0hUX1RSSU1fQllfQ0hBUltfY2hhcl0gPSByZWdleFJpZ2h0ID0gbmV3IFJlZ0V4cCBfY2hhcisnJytfY2hhcisnKiQnXG4gICAgICAgIHJlZ2V4UmlnaHQubGFzdEluZGV4ID0gMFxuICAgICAgICByZXR1cm4gc3RyLnJlcGxhY2UocmVnZXhMZWZ0LCAnJykucmVwbGFjZShyZWdleFJpZ2h0LCAnJylcblxuXG4gICAgIyBUcmltcyB0aGUgZ2l2ZW4gc3RyaW5nIG9uIHRoZSBsZWZ0IHNpZGVcbiAgICAjXG4gICAgIyBAcGFyYW0gW1N0cmluZ10gc3RyIFRoZSBzdHJpbmcgdG8gdHJpbVxuICAgICMgQHBhcmFtIFtTdHJpbmddIF9jaGFyIFRoZSBjaGFyYWN0ZXIgdG8gdXNlIGZvciB0cmltbWluZyAoZGVmYXVsdDogJ1xcXFxzJylcbiAgICAjXG4gICAgIyBAcmV0dXJuIFtTdHJpbmddIEEgdHJpbW1lZCBzdHJpbmdcbiAgICAjXG4gICAgQGx0cmltOiAoc3RyLCBfY2hhciA9ICdcXFxccycpIC0+XG4gICAgICAgIHJlZ2V4TGVmdCA9IEBSRUdFWF9MRUZUX1RSSU1fQllfQ0hBUltfY2hhcl1cbiAgICAgICAgdW5sZXNzIHJlZ2V4TGVmdD9cbiAgICAgICAgICAgIEBSRUdFWF9MRUZUX1RSSU1fQllfQ0hBUltfY2hhcl0gPSByZWdleExlZnQgPSBuZXcgUmVnRXhwICdeJytfY2hhcisnJytfY2hhcisnKidcbiAgICAgICAgcmVnZXhMZWZ0Lmxhc3RJbmRleCA9IDBcbiAgICAgICAgcmV0dXJuIHN0ci5yZXBsYWNlKHJlZ2V4TGVmdCwgJycpXG5cblxuICAgICMgVHJpbXMgdGhlIGdpdmVuIHN0cmluZyBvbiB0aGUgcmlnaHQgc2lkZVxuICAgICNcbiAgICAjIEBwYXJhbSBbU3RyaW5nXSBzdHIgVGhlIHN0cmluZyB0byB0cmltXG4gICAgIyBAcGFyYW0gW1N0cmluZ10gX2NoYXIgVGhlIGNoYXJhY3RlciB0byB1c2UgZm9yIHRyaW1taW5nIChkZWZhdWx0OiAnXFxcXHMnKVxuICAgICNcbiAgICAjIEByZXR1cm4gW1N0cmluZ10gQSB0cmltbWVkIHN0cmluZ1xuICAgICNcbiAgICBAcnRyaW06IChzdHIsIF9jaGFyID0gJ1xcXFxzJykgLT5cbiAgICAgICAgcmVnZXhSaWdodCA9IEBSRUdFWF9SSUdIVF9UUklNX0JZX0NIQVJbX2NoYXJdXG4gICAgICAgIHVubGVzcyByZWdleFJpZ2h0P1xuICAgICAgICAgICAgQFJFR0VYX1JJR0hUX1RSSU1fQllfQ0hBUltfY2hhcl0gPSByZWdleFJpZ2h0ID0gbmV3IFJlZ0V4cCBfY2hhcisnJytfY2hhcisnKiQnXG4gICAgICAgIHJlZ2V4UmlnaHQubGFzdEluZGV4ID0gMFxuICAgICAgICByZXR1cm4gc3RyLnJlcGxhY2UocmVnZXhSaWdodCwgJycpXG5cblxuICAgICMgQ2hlY2tzIGlmIHRoZSBnaXZlbiB2YWx1ZSBpcyBlbXB0eSAobnVsbCwgdW5kZWZpbmVkLCBlbXB0eSBzdHJpbmcsIHN0cmluZyAnMCcpXG4gICAgI1xuICAgICMgQHBhcmFtIFtPYmplY3RdIHZhbHVlIFRoZSB2YWx1ZSB0byBjaGVja1xuICAgICNcbiAgICAjIEByZXR1cm4gW0Jvb2xlYW5dIHRydWUgaWYgdGhlIHZhbHVlIGlzIGVtcHR5XG4gICAgI1xuICAgIEBpc0VtcHR5OiAodmFsdWUpIC0+XG4gICAgICAgIHJldHVybiBub3QodmFsdWUpIG9yIHZhbHVlIGlzICcnIG9yIHZhbHVlIGlzICcwJyBvciAodmFsdWUgaW5zdGFuY2VvZiBBcnJheSBhbmQgdmFsdWUubGVuZ3RoIGlzIDApXG5cblxuICAgICMgQ291bnRzIHRoZSBudW1iZXIgb2Ygb2NjdXJlbmNlcyBvZiBzdWJTdHJpbmcgaW5zaWRlIHN0cmluZ1xuICAgICNcbiAgICAjIEBwYXJhbSBbU3RyaW5nXSBzdHJpbmcgVGhlIHN0cmluZyB3aGVyZSB0byBjb3VudCBvY2N1cmVuY2VzXG4gICAgIyBAcGFyYW0gW1N0cmluZ10gc3ViU3RyaW5nIFRoZSBzdWJTdHJpbmcgdG8gY291bnRcbiAgICAjIEBwYXJhbSBbSW50ZWdlcl0gc3RhcnQgVGhlIHN0YXJ0IGluZGV4XG4gICAgIyBAcGFyYW0gW0ludGVnZXJdIGxlbmd0aCBUaGUgc3RyaW5nIGxlbmd0aCB1bnRpbCB3aGVyZSB0byBjb3VudFxuICAgICNcbiAgICAjIEByZXR1cm4gW0ludGVnZXJdIFRoZSBudW1iZXIgb2Ygb2NjdXJlbmNlc1xuICAgICNcbiAgICBAc3ViU3RyQ291bnQ6IChzdHJpbmcsIHN1YlN0cmluZywgc3RhcnQsIGxlbmd0aCkgLT5cbiAgICAgICAgYyA9IDBcbiAgICAgICAgXG4gICAgICAgIHN0cmluZyA9ICcnICsgc3RyaW5nXG4gICAgICAgIHN1YlN0cmluZyA9ICcnICsgc3ViU3RyaW5nXG4gICAgICAgIFxuICAgICAgICBpZiBzdGFydD9cbiAgICAgICAgICAgIHN0cmluZyA9IHN0cmluZ1tzdGFydC4uXVxuICAgICAgICBpZiBsZW5ndGg/XG4gICAgICAgICAgICBzdHJpbmcgPSBzdHJpbmdbMC4uLmxlbmd0aF1cbiAgICAgICAgXG4gICAgICAgIGxlbiA9IHN0cmluZy5sZW5ndGhcbiAgICAgICAgc3VibGVuID0gc3ViU3RyaW5nLmxlbmd0aFxuICAgICAgICBmb3IgaSBpbiBbMC4uLmxlbl1cbiAgICAgICAgICAgIGlmIHN1YlN0cmluZyBpcyBzdHJpbmdbaS4uLnN1Ymxlbl1cbiAgICAgICAgICAgICAgICBjKytcbiAgICAgICAgICAgICAgICBpICs9IHN1YmxlbiAtIDFcbiAgICAgICAgXG4gICAgICAgIHJldHVybiBjXG5cblxuICAgICMgUmV0dXJucyB0cnVlIGlmIGlucHV0IGlzIG9ubHkgY29tcG9zZWQgb2YgZGlnaXRzXG4gICAgI1xuICAgICMgQHBhcmFtIFtPYmplY3RdIGlucHV0IFRoZSB2YWx1ZSB0byB0ZXN0XG4gICAgI1xuICAgICMgQHJldHVybiBbQm9vbGVhbl0gdHJ1ZSBpZiBpbnB1dCBpcyBvbmx5IGNvbXBvc2VkIG9mIGRpZ2l0c1xuICAgICNcbiAgICBAaXNEaWdpdHM6IChpbnB1dCkgLT5cbiAgICAgICAgQFJFR0VYX0RJR0lUUy5sYXN0SW5kZXggPSAwXG4gICAgICAgIHJldHVybiBAUkVHRVhfRElHSVRTLnRlc3QgaW5wdXRcblxuXG4gICAgIyBEZWNvZGUgb2N0YWwgdmFsdWVcbiAgICAjXG4gICAgIyBAcGFyYW0gW1N0cmluZ10gaW5wdXQgVGhlIHZhbHVlIHRvIGRlY29kZVxuICAgICNcbiAgICAjIEByZXR1cm4gW0ludGVnZXJdIFRoZSBkZWNvZGVkIHZhbHVlXG4gICAgI1xuICAgIEBvY3REZWM6IChpbnB1dCkgLT5cbiAgICAgICAgQFJFR0VYX09DVEFMLmxhc3RJbmRleCA9IDBcbiAgICAgICAgcmV0dXJuIHBhcnNlSW50KChpbnB1dCsnJykucmVwbGFjZShAUkVHRVhfT0NUQUwsICcnKSwgOClcblxuXG4gICAgIyBEZWNvZGUgaGV4YWRlY2ltYWwgdmFsdWVcbiAgICAjXG4gICAgIyBAcGFyYW0gW1N0cmluZ10gaW5wdXQgVGhlIHZhbHVlIHRvIGRlY29kZVxuICAgICNcbiAgICAjIEByZXR1cm4gW0ludGVnZXJdIFRoZSBkZWNvZGVkIHZhbHVlXG4gICAgI1xuICAgIEBoZXhEZWM6IChpbnB1dCkgLT5cbiAgICAgICAgQFJFR0VYX0hFWEFERUNJTUFMLmxhc3RJbmRleCA9IDBcbiAgICAgICAgaW5wdXQgPSBAdHJpbShpbnB1dClcbiAgICAgICAgaWYgKGlucHV0KycnKVswLi4uMl0gaXMgJzB4JyB0aGVuIGlucHV0ID0gKGlucHV0KycnKVsyLi5dXG4gICAgICAgIHJldHVybiBwYXJzZUludCgoaW5wdXQrJycpLnJlcGxhY2UoQFJFR0VYX0hFWEFERUNJTUFMLCAnJyksIDE2KVxuXG5cbiAgICAjIEdldCB0aGUgVVRGLTggY2hhcmFjdGVyIGZvciB0aGUgZ2l2ZW4gY29kZSBwb2ludC5cbiAgICAjXG4gICAgIyBAcGFyYW0gW0ludGVnZXJdIGMgVGhlIHVuaWNvZGUgY29kZSBwb2ludFxuICAgICNcbiAgICAjIEByZXR1cm4gW1N0cmluZ10gVGhlIGNvcnJlc3BvbmRpbmcgVVRGLTggY2hhcmFjdGVyXG4gICAgI1xuICAgIEB1dGY4Y2hyOiAoYykgLT5cbiAgICAgICAgY2ggPSBTdHJpbmcuZnJvbUNoYXJDb2RlXG4gICAgICAgIGlmIDB4ODAgPiAoYyAlPSAweDIwMDAwMClcbiAgICAgICAgICAgIHJldHVybiBjaChjKVxuICAgICAgICBpZiAweDgwMCA+IGNcbiAgICAgICAgICAgIHJldHVybiBjaCgweEMwIHwgYz4+NikgKyBjaCgweDgwIHwgYyAmIDB4M0YpXG4gICAgICAgIGlmIDB4MTAwMDAgPiBjXG4gICAgICAgICAgICByZXR1cm4gY2goMHhFMCB8IGM+PjEyKSArIGNoKDB4ODAgfCBjPj42ICYgMHgzRikgKyBjaCgweDgwIHwgYyAmIDB4M0YpXG5cbiAgICAgICAgcmV0dXJuIGNoKDB4RjAgfCBjPj4xOCkgKyBjaCgweDgwIHwgYz4+MTIgJiAweDNGKSArIGNoKDB4ODAgfCBjPj42ICYgMHgzRikgKyBjaCgweDgwIHwgYyAmIDB4M0YpXG5cblxuICAgICMgUmV0dXJucyB0aGUgYm9vbGVhbiB2YWx1ZSBlcXVpdmFsZW50IHRvIHRoZSBnaXZlbiBpbnB1dFxuICAgICNcbiAgICAjIEBwYXJhbSBbU3RyaW5nfE9iamVjdF0gICAgaW5wdXQgICAgICAgVGhlIGlucHV0IHZhbHVlXG4gICAgIyBAcGFyYW0gW0Jvb2xlYW5dICAgICAgICAgIHN0cmljdCAgICAgIElmIHNldCB0byBmYWxzZSwgYWNjZXB0ICd5ZXMnIGFuZCAnbm8nIGFzIGJvb2xlYW4gdmFsdWVzXG4gICAgI1xuICAgICMgQHJldHVybiBbQm9vbGVhbl0gICAgICAgICB0aGUgYm9vbGVhbiB2YWx1ZVxuICAgICNcbiAgICBAcGFyc2VCb29sZWFuOiAoaW5wdXQsIHN0cmljdCA9IHRydWUpIC0+XG4gICAgICAgIGlmIHR5cGVvZihpbnB1dCkgaXMgJ3N0cmluZydcbiAgICAgICAgICAgIGxvd2VySW5wdXQgPSBpbnB1dC50b0xvd2VyQ2FzZSgpXG4gICAgICAgICAgICBpZiBub3Qgc3RyaWN0XG4gICAgICAgICAgICAgICAgaWYgbG93ZXJJbnB1dCBpcyAnbm8nIHRoZW4gcmV0dXJuIGZhbHNlXG4gICAgICAgICAgICBpZiBsb3dlcklucHV0IGlzICcwJyB0aGVuIHJldHVybiBmYWxzZVxuICAgICAgICAgICAgaWYgbG93ZXJJbnB1dCBpcyAnZmFsc2UnIHRoZW4gcmV0dXJuIGZhbHNlXG4gICAgICAgICAgICBpZiBsb3dlcklucHV0IGlzICcnIHRoZW4gcmV0dXJuIGZhbHNlXG4gICAgICAgICAgICByZXR1cm4gdHJ1ZVxuICAgICAgICByZXR1cm4gISFpbnB1dFxuXG5cblxuICAgICMgUmV0dXJucyB0cnVlIGlmIGlucHV0IGlzIG51bWVyaWNcbiAgICAjXG4gICAgIyBAcGFyYW0gW09iamVjdF0gaW5wdXQgVGhlIHZhbHVlIHRvIHRlc3RcbiAgICAjXG4gICAgIyBAcmV0dXJuIFtCb29sZWFuXSB0cnVlIGlmIGlucHV0IGlzIG51bWVyaWNcbiAgICAjXG4gICAgQGlzTnVtZXJpYzogKGlucHV0KSAtPlxuICAgICAgICBAUkVHRVhfU1BBQ0VTLmxhc3RJbmRleCA9IDBcbiAgICAgICAgcmV0dXJuIHR5cGVvZihpbnB1dCkgaXMgJ251bWJlcicgb3IgdHlwZW9mKGlucHV0KSBpcyAnc3RyaW5nJyBhbmQgIWlzTmFOKGlucHV0KSBhbmQgaW5wdXQucmVwbGFjZShAUkVHRVhfU1BBQ0VTLCAnJykgaXNudCAnJ1xuXG5cbiAgICAjIFJldHVybnMgYSBwYXJzZWQgZGF0ZSBmcm9tIHRoZSBnaXZlbiBzdHJpbmdcbiAgICAjXG4gICAgIyBAcGFyYW0gW1N0cmluZ10gc3RyIFRoZSBkYXRlIHN0cmluZyB0byBwYXJzZVxuICAgICNcbiAgICAjIEByZXR1cm4gW0RhdGVdIFRoZSBwYXJzZWQgZGF0ZSBvciBudWxsIGlmIHBhcnNpbmcgZmFpbGVkXG4gICAgI1xuICAgIEBzdHJpbmdUb0RhdGU6IChzdHIpIC0+XG4gICAgICAgIHVubGVzcyBzdHI/Lmxlbmd0aFxuICAgICAgICAgICAgcmV0dXJuIG51bGxcblxuICAgICAgICAjIFBlcmZvcm0gcmVndWxhciBleHByZXNzaW9uIHBhdHRlcm5cbiAgICAgICAgaW5mbyA9IEBQQVRURVJOX0RBVEUuZXhlYyBzdHJcbiAgICAgICAgdW5sZXNzIGluZm9cbiAgICAgICAgICAgIHJldHVybiBudWxsXG5cbiAgICAgICAgIyBFeHRyYWN0IHllYXIsIG1vbnRoLCBkYXlcbiAgICAgICAgeWVhciA9IHBhcnNlSW50IGluZm8ueWVhciwgMTBcbiAgICAgICAgbW9udGggPSBwYXJzZUludChpbmZvLm1vbnRoLCAxMCkgLSAxICMgSW4gamF2YXNjcmlwdCwgamFudWFyeSBpcyAwLCBmZWJydWFyeSAxLCBldGMuLi5cbiAgICAgICAgZGF5ID0gcGFyc2VJbnQgaW5mby5kYXksIDEwXG5cbiAgICAgICAgIyBJZiBubyBob3VyIGlzIGdpdmVuLCByZXR1cm4gYSBkYXRlIHdpdGggZGF5IHByZWNpc2lvblxuICAgICAgICB1bmxlc3MgaW5mby5ob3VyP1xuICAgICAgICAgICAgZGF0ZSA9IG5ldyBEYXRlIERhdGUuVVRDKHllYXIsIG1vbnRoLCBkYXkpXG4gICAgICAgICAgICByZXR1cm4gZGF0ZVxuXG4gICAgICAgICMgRXh0cmFjdCBob3VyLCBtaW51dGUsIHNlY29uZFxuICAgICAgICBob3VyID0gcGFyc2VJbnQgaW5mby5ob3VyLCAxMFxuICAgICAgICBtaW51dGUgPSBwYXJzZUludCBpbmZvLm1pbnV0ZSwgMTBcbiAgICAgICAgc2Vjb25kID0gcGFyc2VJbnQgaW5mby5zZWNvbmQsIDEwXG5cbiAgICAgICAgIyBFeHRyYWN0IGZyYWN0aW9uLCBpZiBnaXZlblxuICAgICAgICBpZiBpbmZvLmZyYWN0aW9uP1xuICAgICAgICAgICAgZnJhY3Rpb24gPSBpbmZvLmZyYWN0aW9uWzAuLi4zXVxuICAgICAgICAgICAgd2hpbGUgZnJhY3Rpb24ubGVuZ3RoIDwgM1xuICAgICAgICAgICAgICAgIGZyYWN0aW9uICs9ICcwJ1xuICAgICAgICAgICAgZnJhY3Rpb24gPSBwYXJzZUludCBmcmFjdGlvbiwgMTBcbiAgICAgICAgZWxzZVxuICAgICAgICAgICAgZnJhY3Rpb24gPSAwXG5cbiAgICAgICAgIyBDb21wdXRlIHRpbWV6b25lIG9mZnNldCBpZiBnaXZlblxuICAgICAgICBpZiBpbmZvLnR6P1xuICAgICAgICAgICAgdHpfaG91ciA9IHBhcnNlSW50IGluZm8udHpfaG91ciwgMTBcbiAgICAgICAgICAgIGlmIGluZm8udHpfbWludXRlP1xuICAgICAgICAgICAgICAgIHR6X21pbnV0ZSA9IHBhcnNlSW50IGluZm8udHpfbWludXRlLCAxMFxuICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAgICAgIHR6X21pbnV0ZSA9IDBcblxuICAgICAgICAgICAgIyBDb21wdXRlIHRpbWV6b25lIGRlbHRhIGluIG1zXG4gICAgICAgICAgICB0el9vZmZzZXQgPSAodHpfaG91ciAqIDYwICsgdHpfbWludXRlKSAqIDYwMDAwXG4gICAgICAgICAgICBpZiAnLScgaXMgaW5mby50el9zaWduXG4gICAgICAgICAgICAgICAgdHpfb2Zmc2V0ICo9IC0xXG5cbiAgICAgICAgIyBDb21wdXRlIGRhdGVcbiAgICAgICAgZGF0ZSA9IG5ldyBEYXRlIERhdGUuVVRDKHllYXIsIG1vbnRoLCBkYXksIGhvdXIsIG1pbnV0ZSwgc2Vjb25kLCBmcmFjdGlvbilcbiAgICAgICAgaWYgdHpfb2Zmc2V0XG4gICAgICAgICAgICBkYXRlLnNldFRpbWUgZGF0ZS5nZXRUaW1lKCkgKyB0el9vZmZzZXRcblxuICAgICAgICByZXR1cm4gZGF0ZVxuXG5cbiAgICAjIFJlcGVhdHMgdGhlIGdpdmVuIHN0cmluZyBhIG51bWJlciBvZiB0aW1lc1xuICAgICNcbiAgICAjIEBwYXJhbSBbU3RyaW5nXSAgIHN0ciAgICAgVGhlIHN0cmluZyB0byByZXBlYXRcbiAgICAjIEBwYXJhbSBbSW50ZWdlcl0gIG51bWJlciAgVGhlIG51bWJlciBvZiB0aW1lcyB0byByZXBlYXQgdGhlIHN0cmluZ1xuICAgICNcbiAgICAjIEByZXR1cm4gW1N0cmluZ10gIFRoZSByZXBlYXRlZCBzdHJpbmdcbiAgICAjXG4gICAgQHN0clJlcGVhdDogKHN0ciwgbnVtYmVyKSAtPlxuICAgICAgICByZXMgPSAnJ1xuICAgICAgICBpID0gMFxuICAgICAgICB3aGlsZSBpIDwgbnVtYmVyXG4gICAgICAgICAgICByZXMgKz0gc3RyXG4gICAgICAgICAgICBpKytcbiAgICAgICAgcmV0dXJuIHJlc1xuXG5cbiAgICAjIFJlYWRzIHRoZSBkYXRhIGZyb20gdGhlIGdpdmVuIGZpbGUgcGF0aCBhbmQgcmV0dXJucyB0aGUgcmVzdWx0IGFzIHN0cmluZ1xuICAgICNcbiAgICAjIEBwYXJhbSBbU3RyaW5nXSAgIHBhdGggICAgICAgIFRoZSBwYXRoIHRvIHRoZSBmaWxlXG4gICAgIyBAcGFyYW0gW0Z1bmN0aW9uXSBjYWxsYmFjayAgICBBIGNhbGxiYWNrIHRvIHJlYWQgZmlsZSBhc3luY2hyb25vdXNseSAob3B0aW9uYWwpXG4gICAgI1xuICAgICMgQHJldHVybiBbU3RyaW5nXSAgVGhlIHJlc3VsdGluZyBkYXRhIGFzIHN0cmluZ1xuICAgICNcbiAgICBAZ2V0U3RyaW5nRnJvbUZpbGU6IChwYXRoLCBjYWxsYmFjayA9IG51bGwpIC0+XG4gICAgICAgIHhociA9IG51bGxcbiAgICAgICAgaWYgd2luZG93P1xuICAgICAgICAgICAgaWYgd2luZG93LlhNTEh0dHBSZXF1ZXN0XG4gICAgICAgICAgICAgICAgeGhyID0gbmV3IFhNTEh0dHBSZXF1ZXN0KClcbiAgICAgICAgICAgIGVsc2UgaWYgd2luZG93LkFjdGl2ZVhPYmplY3RcbiAgICAgICAgICAgICAgICBmb3IgbmFtZSBpbiBbXCJNc3htbDIuWE1MSFRUUC42LjBcIiwgXCJNc3htbDIuWE1MSFRUUC4zLjBcIiwgXCJNc3htbDIuWE1MSFRUUFwiLCBcIk1pY3Jvc29mdC5YTUxIVFRQXCJdXG4gICAgICAgICAgICAgICAgICAgIHRyeVxuICAgICAgICAgICAgICAgICAgICAgICAgeGhyID0gbmV3IEFjdGl2ZVhPYmplY3QobmFtZSlcblxuICAgICAgICBpZiB4aHI/XG4gICAgICAgICAgICAjIEJyb3dzZXJcbiAgICAgICAgICAgIGlmIGNhbGxiYWNrP1xuICAgICAgICAgICAgICAgICMgQXN5bmNcbiAgICAgICAgICAgICAgICB4aHIub25yZWFkeXN0YXRlY2hhbmdlID0gLT5cbiAgICAgICAgICAgICAgICAgICAgaWYgeGhyLnJlYWR5U3RhdGUgaXMgNFxuICAgICAgICAgICAgICAgICAgICAgICAgaWYgeGhyLnN0YXR1cyBpcyAyMDAgb3IgeGhyLnN0YXR1cyBpcyAwXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FsbGJhY2soeGhyLnJlc3BvbnNlVGV4dClcbiAgICAgICAgICAgICAgICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYWxsYmFjayhudWxsKVxuICAgICAgICAgICAgICAgIHhoci5vcGVuICdHRVQnLCBwYXRoLCB0cnVlXG4gICAgICAgICAgICAgICAgeGhyLnNlbmQgbnVsbFxuICAgICAgICAgICAgXG4gICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAgIyBTeW5jXG4gICAgICAgICAgICAgICAgeGhyLm9wZW4gJ0dFVCcsIHBhdGgsIGZhbHNlXG4gICAgICAgICAgICAgICAgeGhyLnNlbmQgbnVsbFxuXG4gICAgICAgICAgICAgICAgaWYgeGhyLnN0YXR1cyBpcyAyMDAgb3IgeGhyLnN0YXR1cyA9PSAwXG4gICAgICAgICAgICAgICAgICAgIHJldHVybiB4aHIucmVzcG9uc2VUZXh0XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gbnVsbFxuICAgICAgICBlbHNlXG4gICAgICAgICAgICAjIE5vZGUuanMtbGlrZVxuICAgICAgICAgICAgcmVxID0gcmVxdWlyZVxuICAgICAgICAgICAgZnMgPSByZXEoJ2ZzJykgIyBQcmV2ZW50IGJyb3dzZXJpZnkgZnJvbSB0cnlpbmcgdG8gbG9hZCAnZnMnIG1vZHVsZVxuICAgICAgICAgICAgaWYgY2FsbGJhY2s/XG4gICAgICAgICAgICAgICAgIyBBc3luY1xuICAgICAgICAgICAgICAgIGZzLnJlYWRGaWxlIHBhdGgsIChlcnIsIGRhdGEpIC0+XG4gICAgICAgICAgICAgICAgICAgIGlmIGVyclxuICAgICAgICAgICAgICAgICAgICAgICAgY2FsbGJhY2sgbnVsbFxuICAgICAgICAgICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAgICAgICAgICBjYWxsYmFjayBTdHJpbmcoZGF0YSlcblxuICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAgICAgICMgU3luY1xuICAgICAgICAgICAgICAgIGRhdGEgPSBmcy5yZWFkRmlsZVN5bmMgcGF0aFxuICAgICAgICAgICAgICAgIGlmIGRhdGE/XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBTdHJpbmcoZGF0YSlcbiAgICAgICAgICAgICAgICByZXR1cm4gbnVsbFxuXG5cblxubW9kdWxlLmV4cG9ydHMgPSBVdGlsc1xuIiwiXG5QYXJzZXIgPSByZXF1aXJlICcuL1BhcnNlcidcbkR1bXBlciA9IHJlcXVpcmUgJy4vRHVtcGVyJ1xuVXRpbHMgID0gcmVxdWlyZSAnLi9VdGlscydcblxuIyBZYW1sIG9mZmVycyBjb252ZW5pZW5jZSBtZXRob2RzIHRvIGxvYWQgYW5kIGR1bXAgWUFNTC5cbiNcbmNsYXNzIFlhbWxcblxuICAgICMgUGFyc2VzIFlBTUwgaW50byBhIEphdmFTY3JpcHQgb2JqZWN0LlxuICAgICNcbiAgICAjIFRoZSBwYXJzZSBtZXRob2QsIHdoZW4gc3VwcGxpZWQgd2l0aCBhIFlBTUwgc3RyaW5nLFxuICAgICMgd2lsbCBkbyBpdHMgYmVzdCB0byBjb252ZXJ0IFlBTUwgaW4gYSBmaWxlIGludG8gYSBKYXZhU2NyaXB0IG9iamVjdC5cbiAgICAjXG4gICAgIyAgVXNhZ2U6XG4gICAgIyAgICAgbXlPYmplY3QgPSBZYW1sLnBhcnNlKCdzb21lOiB5YW1sJyk7XG4gICAgIyAgICAgY29uc29sZS5sb2cobXlPYmplY3QpO1xuICAgICNcbiAgICAjIEBwYXJhbSBbU3RyaW5nXSAgIGlucHV0ICAgICAgICAgICAgICAgICAgIEEgc3RyaW5nIGNvbnRhaW5pbmcgWUFNTFxuICAgICMgQHBhcmFtIFtCb29sZWFuXSAgZXhjZXB0aW9uT25JbnZhbGlkVHlwZSAgdHJ1ZSBpZiBhbiBleGNlcHRpb24gbXVzdCBiZSB0aHJvd24gb24gaW52YWxpZCB0eXBlcywgZmFsc2Ugb3RoZXJ3aXNlXG4gICAgIyBAcGFyYW0gW0Z1bmN0aW9uXSBvYmplY3REZWNvZGVyICAgICAgICAgICBBIGZ1bmN0aW9uIHRvIGRlc2VyaWFsaXplIGN1c3RvbSBvYmplY3RzLCBudWxsIG90aGVyd2lzZVxuICAgICNcbiAgICAjIEByZXR1cm4gW09iamVjdF0gIFRoZSBZQU1MIGNvbnZlcnRlZCB0byBhIEphdmFTY3JpcHQgb2JqZWN0XG4gICAgI1xuICAgICMgQHRocm93IFtQYXJzZUV4Y2VwdGlvbl0gSWYgdGhlIFlBTUwgaXMgbm90IHZhbGlkXG4gICAgI1xuICAgIEBwYXJzZTogKGlucHV0LCBleGNlcHRpb25PbkludmFsaWRUeXBlID0gZmFsc2UsIG9iamVjdERlY29kZXIgPSBudWxsKSAtPlxuICAgICAgICByZXR1cm4gbmV3IFBhcnNlcigpLnBhcnNlKGlucHV0LCBleGNlcHRpb25PbkludmFsaWRUeXBlLCBvYmplY3REZWNvZGVyKVxuXG5cbiAgICAjIFBhcnNlcyBZQU1MIGZyb20gZmlsZSBwYXRoIGludG8gYSBKYXZhU2NyaXB0IG9iamVjdC5cbiAgICAjXG4gICAgIyBUaGUgcGFyc2VGaWxlIG1ldGhvZCwgd2hlbiBzdXBwbGllZCB3aXRoIGEgWUFNTCBmaWxlLFxuICAgICMgd2lsbCBkbyBpdHMgYmVzdCB0byBjb252ZXJ0IFlBTUwgaW4gYSBmaWxlIGludG8gYSBKYXZhU2NyaXB0IG9iamVjdC5cbiAgICAjXG4gICAgIyAgVXNhZ2U6XG4gICAgIyAgICAgbXlPYmplY3QgPSBZYW1sLnBhcnNlRmlsZSgnY29uZmlnLnltbCcpO1xuICAgICMgICAgIGNvbnNvbGUubG9nKG15T2JqZWN0KTtcbiAgICAjXG4gICAgIyBAcGFyYW0gW1N0cmluZ10gICBwYXRoICAgICAgICAgICAgICAgICAgICBBIGZpbGUgcGF0aCBwb2ludGluZyB0byBhIHZhbGlkIFlBTUwgZmlsZVxuICAgICMgQHBhcmFtIFtCb29sZWFuXSAgZXhjZXB0aW9uT25JbnZhbGlkVHlwZSAgdHJ1ZSBpZiBhbiBleGNlcHRpb24gbXVzdCBiZSB0aHJvd24gb24gaW52YWxpZCB0eXBlcywgZmFsc2Ugb3RoZXJ3aXNlXG4gICAgIyBAcGFyYW0gW0Z1bmN0aW9uXSBvYmplY3REZWNvZGVyICAgICAgICAgICBBIGZ1bmN0aW9uIHRvIGRlc2VyaWFsaXplIGN1c3RvbSBvYmplY3RzLCBudWxsIG90aGVyd2lzZVxuICAgICNcbiAgICAjIEByZXR1cm4gW09iamVjdF0gIFRoZSBZQU1MIGNvbnZlcnRlZCB0byBhIEphdmFTY3JpcHQgb2JqZWN0IG9yIG51bGwgaWYgdGhlIGZpbGUgZG9lc24ndCBleGlzdC5cbiAgICAjXG4gICAgIyBAdGhyb3cgW1BhcnNlRXhjZXB0aW9uXSBJZiB0aGUgWUFNTCBpcyBub3QgdmFsaWRcbiAgICAjXG4gICAgQHBhcnNlRmlsZTogKHBhdGgsIGNhbGxiYWNrID0gbnVsbCwgZXhjZXB0aW9uT25JbnZhbGlkVHlwZSA9IGZhbHNlLCBvYmplY3REZWNvZGVyID0gbnVsbCkgLT5cbiAgICAgICAgaWYgY2FsbGJhY2s/XG4gICAgICAgICAgICAjIEFzeW5jXG4gICAgICAgICAgICBVdGlscy5nZXRTdHJpbmdGcm9tRmlsZSBwYXRoLCAoaW5wdXQpID0+XG4gICAgICAgICAgICAgICAgcmVzdWx0ID0gbnVsbFxuICAgICAgICAgICAgICAgIGlmIGlucHV0P1xuICAgICAgICAgICAgICAgICAgICByZXN1bHQgPSBAcGFyc2UgaW5wdXQsIGV4Y2VwdGlvbk9uSW52YWxpZFR5cGUsIG9iamVjdERlY29kZXJcbiAgICAgICAgICAgICAgICBjYWxsYmFjayByZXN1bHRcbiAgICAgICAgICAgICAgICByZXR1cm5cbiAgICAgICAgZWxzZVxuICAgICAgICAgICAgIyBTeW5jXG4gICAgICAgICAgICBpbnB1dCA9IFV0aWxzLmdldFN0cmluZ0Zyb21GaWxlIHBhdGhcbiAgICAgICAgICAgIGlmIGlucHV0P1xuICAgICAgICAgICAgICAgIHJldHVybiBAcGFyc2UgaW5wdXQsIGV4Y2VwdGlvbk9uSW52YWxpZFR5cGUsIG9iamVjdERlY29kZXJcbiAgICAgICAgICAgIHJldHVybiBudWxsXG5cblxuICAgICMgRHVtcHMgYSBKYXZhU2NyaXB0IG9iamVjdCB0byBhIFlBTUwgc3RyaW5nLlxuICAgICNcbiAgICAjIFRoZSBkdW1wIG1ldGhvZCwgd2hlbiBzdXBwbGllZCB3aXRoIGFuIG9iamVjdCwgd2lsbCBkbyBpdHMgYmVzdFxuICAgICMgdG8gY29udmVydCB0aGUgb2JqZWN0IGludG8gZnJpZW5kbHkgWUFNTC5cbiAgICAjXG4gICAgIyBAcGFyYW0gW09iamVjdF0gICBpbnB1dCAgICAgICAgICAgICAgICAgICBKYXZhU2NyaXB0IG9iamVjdFxuICAgICMgQHBhcmFtIFtJbnRlZ2VyXSAgaW5saW5lICAgICAgICAgICAgICAgICAgVGhlIGxldmVsIHdoZXJlIHlvdSBzd2l0Y2ggdG8gaW5saW5lIFlBTUxcbiAgICAjIEBwYXJhbSBbSW50ZWdlcl0gIGluZGVudCAgICAgICAgICAgICAgICAgIFRoZSBhbW91bnQgb2Ygc3BhY2VzIHRvIHVzZSBmb3IgaW5kZW50YXRpb24gb2YgbmVzdGVkIG5vZGVzLlxuICAgICMgQHBhcmFtIFtCb29sZWFuXSAgZXhjZXB0aW9uT25JbnZhbGlkVHlwZSAgdHJ1ZSBpZiBhbiBleGNlcHRpb24gbXVzdCBiZSB0aHJvd24gb24gaW52YWxpZCB0eXBlcyAoYSBKYXZhU2NyaXB0IHJlc291cmNlIG9yIG9iamVjdCksIGZhbHNlIG90aGVyd2lzZVxuICAgICMgQHBhcmFtIFtGdW5jdGlvbl0gb2JqZWN0RW5jb2RlciAgICAgICAgICAgQSBmdW5jdGlvbiB0byBzZXJpYWxpemUgY3VzdG9tIG9iamVjdHMsIG51bGwgb3RoZXJ3aXNlXG4gICAgI1xuICAgICMgQHJldHVybiBbU3RyaW5nXSAgQSBZQU1MIHN0cmluZyByZXByZXNlbnRpbmcgdGhlIG9yaWdpbmFsIEphdmFTY3JpcHQgb2JqZWN0XG4gICAgI1xuICAgIEBkdW1wOiAoaW5wdXQsIGlubGluZSA9IDIsIGluZGVudCA9IDQsIGV4Y2VwdGlvbk9uSW52YWxpZFR5cGUgPSBmYWxzZSwgb2JqZWN0RW5jb2RlciA9IG51bGwpIC0+XG4gICAgICAgIHlhbWwgPSBuZXcgRHVtcGVyKClcbiAgICAgICAgeWFtbC5pbmRlbnRhdGlvbiA9IGluZGVudFxuXG4gICAgICAgIHJldHVybiB5YW1sLmR1bXAoaW5wdXQsIGlubGluZSwgMCwgZXhjZXB0aW9uT25JbnZhbGlkVHlwZSwgb2JqZWN0RW5jb2RlcilcblxuXG4gICAgIyBSZWdpc3RlcnMgLnltbCBleHRlbnNpb24gdG8gd29yayB3aXRoIG5vZGUncyByZXF1aXJlKCkgZnVuY3Rpb24uXG4gICAgI1xuICAgIEByZWdpc3RlcjogLT5cbiAgICAgICAgcmVxdWlyZV9oYW5kbGVyID0gKG1vZHVsZSwgZmlsZW5hbWUpIC0+XG4gICAgICAgICAgICAjIEZpbGwgaW4gcmVzdWx0XG4gICAgICAgICAgICBtb2R1bGUuZXhwb3J0cyA9IFlBTUwucGFyc2VGaWxlIGZpbGVuYW1lXG5cbiAgICAgICAgIyBSZWdpc3RlciByZXF1aXJlIGV4dGVuc2lvbnMgb25seSBpZiB3ZSdyZSBvbiBub2RlLmpzXG4gICAgICAgICMgaGFjayBmb3IgYnJvd3NlcmlmeVxuICAgICAgICBpZiByZXF1aXJlPy5leHRlbnNpb25zP1xuICAgICAgICAgICAgcmVxdWlyZS5leHRlbnNpb25zWycueW1sJ10gPSByZXF1aXJlX2hhbmRsZXJcbiAgICAgICAgICAgIHJlcXVpcmUuZXh0ZW5zaW9uc1snLnlhbWwnXSA9IHJlcXVpcmVfaGFuZGxlclxuXG5cbiAgICAjIEFsaWFzIG9mIGR1bXAoKSBtZXRob2QgZm9yIGNvbXBhdGliaWxpdHkgcmVhc29ucy5cbiAgICAjXG4gICAgQHN0cmluZ2lmeTogKGlucHV0LCBpbmxpbmUsIGluZGVudCwgZXhjZXB0aW9uT25JbnZhbGlkVHlwZSwgb2JqZWN0RW5jb2RlcikgLT5cbiAgICAgICAgcmV0dXJuIEBkdW1wIGlucHV0LCBpbmxpbmUsIGluZGVudCwgZXhjZXB0aW9uT25JbnZhbGlkVHlwZSwgb2JqZWN0RW5jb2RlclxuXG5cbiAgICAjIEFsaWFzIG9mIHBhcnNlRmlsZSgpIG1ldGhvZCBmb3IgY29tcGF0aWJpbGl0eSByZWFzb25zLlxuICAgICNcbiAgICBAbG9hZDogKHBhdGgsIGNhbGxiYWNrLCBleGNlcHRpb25PbkludmFsaWRUeXBlLCBvYmplY3REZWNvZGVyKSAtPlxuICAgICAgICByZXR1cm4gQHBhcnNlRmlsZSBwYXRoLCBjYWxsYmFjaywgZXhjZXB0aW9uT25JbnZhbGlkVHlwZSwgb2JqZWN0RGVjb2RlclxuXG5cbiMgRXhwb3NlIFlBTUwgbmFtZXNwYWNlIHRvIGJyb3dzZXJcbndpbmRvdz8uWUFNTCA9IFlhbWxcblxuIyBOb3QgaW4gdGhlIGJyb3dzZXI/XG51bmxlc3Mgd2luZG93P1xuICAgIEBZQU1MID0gWWFtbFxuXG5tb2R1bGUuZXhwb3J0cyA9IFlhbWxcbiJdfQ==
diff --git a/src/main/webapp/scripts/lib/yamljs/dist/yaml.js b/src/main/webapp/scripts/lib/yamljs/dist/yaml.js
new file mode 100755
index 0000000000000000000000000000000000000000..f5f4ca87ae2807b2cb235dc7beabb3cf5d0e58ab
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/dist/yaml.js
@@ -0,0 +1,1859 @@
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+var Dumper, Inline, Utils;
+
+Utils = require('./Utils');
+
+Inline = require('./Inline');
+
+Dumper = (function() {
+  function Dumper() {}
+
+  Dumper.indentation = 4;
+
+  Dumper.prototype.dump = function(input, inline, indent, exceptionOnInvalidType, objectEncoder) {
+    var i, key, len, output, prefix, value, willBeInlined;
+    if (inline == null) {
+      inline = 0;
+    }
+    if (indent == null) {
+      indent = 0;
+    }
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectEncoder == null) {
+      objectEncoder = null;
+    }
+    output = '';
+    prefix = (indent ? Utils.strRepeat(' ', indent) : '');
+    if (inline <= 0 || typeof input !== 'object' || input instanceof Date || Utils.isEmpty(input)) {
+      output += prefix + Inline.dump(input, exceptionOnInvalidType, objectEncoder);
+    } else {
+      if (input instanceof Array) {
+        for (i = 0, len = input.length; i < len; i++) {
+          value = input[i];
+          willBeInlined = inline - 1 <= 0 || typeof value !== 'object' || Utils.isEmpty(value);
+          output += prefix + '-' + (willBeInlined ? ' ' : "\n") + this.dump(value, inline - 1, (willBeInlined ? 0 : indent + this.indentation), exceptionOnInvalidType, objectEncoder) + (willBeInlined ? "\n" : '');
+        }
+      } else {
+        for (key in input) {
+          value = input[key];
+          willBeInlined = inline - 1 <= 0 || typeof value !== 'object' || Utils.isEmpty(value);
+          output += prefix + Inline.dump(key, exceptionOnInvalidType, objectEncoder) + ':' + (willBeInlined ? ' ' : "\n") + this.dump(value, inline - 1, (willBeInlined ? 0 : indent + this.indentation), exceptionOnInvalidType, objectEncoder) + (willBeInlined ? "\n" : '');
+        }
+      }
+    }
+    return output;
+  };
+
+  return Dumper;
+
+})();
+
+module.exports = Dumper;
+
+
+},{"./Inline":5,"./Utils":9}],2:[function(require,module,exports){
+var Escaper, Pattern;
+
+Pattern = require('./Pattern');
+
+Escaper = (function() {
+  var ch;
+
+  function Escaper() {}
+
+  Escaper.LIST_ESCAPEES = ['\\', '\\\\', '\\"', '"', "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", (ch = String.fromCharCode)(0x0085), ch(0x00A0), ch(0x2028), ch(0x2029)];
+
+  Escaper.LIST_ESCAPED = ['\\\\', '\\"', '\\"', '\\"', "\\0", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\a", "\\b", "\\t", "\\n", "\\v", "\\f", "\\r", "\\x0e", "\\x0f", "\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17", "\\x18", "\\x19", "\\x1a", "\\e", "\\x1c", "\\x1d", "\\x1e", "\\x1f", "\\N", "\\_", "\\L", "\\P"];
+
+  Escaper.MAPPING_ESCAPEES_TO_ESCAPED = (function() {
+    var i, j, mapping, ref;
+    mapping = {};
+    for (i = j = 0, ref = Escaper.LIST_ESCAPEES.length; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
+      mapping[Escaper.LIST_ESCAPEES[i]] = Escaper.LIST_ESCAPED[i];
+    }
+    return mapping;
+  })();
+
+  Escaper.PATTERN_CHARACTERS_TO_ESCAPE = new Pattern('[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9');
+
+  Escaper.PATTERN_MAPPING_ESCAPEES = new Pattern(Escaper.LIST_ESCAPEES.join('|').split('\\').join('\\\\'));
+
+  Escaper.PATTERN_SINGLE_QUOTING = new Pattern('[\\s\'":{}[\\],&*#?]|^[-?|<>=!%@`]');
+
+  Escaper.requiresDoubleQuoting = function(value) {
+    return this.PATTERN_CHARACTERS_TO_ESCAPE.test(value);
+  };
+
+  Escaper.escapeWithDoubleQuotes = function(value) {
+    var result;
+    result = this.PATTERN_MAPPING_ESCAPEES.replace(value, (function(_this) {
+      return function(str) {
+        return _this.MAPPING_ESCAPEES_TO_ESCAPED[str];
+      };
+    })(this));
+    return '"' + result + '"';
+  };
+
+  Escaper.requiresSingleQuoting = function(value) {
+    return this.PATTERN_SINGLE_QUOTING.test(value);
+  };
+
+  Escaper.escapeWithSingleQuotes = function(value) {
+    return "'" + value.replace(/'/g, "''") + "'";
+  };
+
+  return Escaper;
+
+})();
+
+module.exports = Escaper;
+
+
+},{"./Pattern":7}],3:[function(require,module,exports){
+var DumpException,
+  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+  hasProp = {}.hasOwnProperty;
+
+DumpException = (function(superClass) {
+  extend(DumpException, superClass);
+
+  function DumpException(message, parsedLine, snippet) {
+    this.message = message;
+    this.parsedLine = parsedLine;
+    this.snippet = snippet;
+  }
+
+  DumpException.prototype.toString = function() {
+    if ((this.parsedLine != null) && (this.snippet != null)) {
+      return '<DumpException> ' + this.message + ' (line ' + this.parsedLine + ': \'' + this.snippet + '\')';
+    } else {
+      return '<DumpException> ' + this.message;
+    }
+  };
+
+  return DumpException;
+
+})(Error);
+
+module.exports = DumpException;
+
+
+},{}],4:[function(require,module,exports){
+var ParseException,
+  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+  hasProp = {}.hasOwnProperty;
+
+ParseException = (function(superClass) {
+  extend(ParseException, superClass);
+
+  function ParseException(message, parsedLine, snippet) {
+    this.message = message;
+    this.parsedLine = parsedLine;
+    this.snippet = snippet;
+  }
+
+  ParseException.prototype.toString = function() {
+    if ((this.parsedLine != null) && (this.snippet != null)) {
+      return '<ParseException> ' + this.message + ' (line ' + this.parsedLine + ': \'' + this.snippet + '\')';
+    } else {
+      return '<ParseException> ' + this.message;
+    }
+  };
+
+  return ParseException;
+
+})(Error);
+
+module.exports = ParseException;
+
+
+},{}],5:[function(require,module,exports){
+var DumpException, Escaper, Inline, ParseException, Pattern, Unescaper, Utils,
+  indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+Pattern = require('./Pattern');
+
+Unescaper = require('./Unescaper');
+
+Escaper = require('./Escaper');
+
+Utils = require('./Utils');
+
+ParseException = require('./Exception/ParseException');
+
+DumpException = require('./Exception/DumpException');
+
+Inline = (function() {
+  function Inline() {}
+
+  Inline.REGEX_QUOTED_STRING = '(?:"(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\']*(?:\'\'[^\']*)*)\')';
+
+  Inline.PATTERN_TRAILING_COMMENTS = new Pattern('^\\s*#.*$');
+
+  Inline.PATTERN_QUOTED_SCALAR = new Pattern('^' + Inline.REGEX_QUOTED_STRING);
+
+  Inline.PATTERN_THOUSAND_NUMERIC_SCALAR = new Pattern('^(-|\\+)?[0-9,]+(\\.[0-9]+)?$');
+
+  Inline.PATTERN_SCALAR_BY_DELIMITERS = {};
+
+  Inline.settings = {};
+
+  Inline.configure = function(exceptionOnInvalidType, objectDecoder) {
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = null;
+    }
+    if (objectDecoder == null) {
+      objectDecoder = null;
+    }
+    this.settings.exceptionOnInvalidType = exceptionOnInvalidType;
+    this.settings.objectDecoder = objectDecoder;
+  };
+
+  Inline.parse = function(value, exceptionOnInvalidType, objectDecoder) {
+    var context, result;
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectDecoder == null) {
+      objectDecoder = null;
+    }
+    this.settings.exceptionOnInvalidType = exceptionOnInvalidType;
+    this.settings.objectDecoder = objectDecoder;
+    if (value == null) {
+      return '';
+    }
+    value = Utils.trim(value);
+    if (0 === value.length) {
+      return '';
+    }
+    context = {
+      exceptionOnInvalidType: exceptionOnInvalidType,
+      objectDecoder: objectDecoder,
+      i: 0
+    };
+    switch (value.charAt(0)) {
+      case '[':
+        result = this.parseSequence(value, context);
+        ++context.i;
+        break;
+      case '{':
+        result = this.parseMapping(value, context);
+        ++context.i;
+        break;
+      default:
+        result = this.parseScalar(value, null, ['"', "'"], context);
+    }
+    if (this.PATTERN_TRAILING_COMMENTS.replace(value.slice(context.i), '') !== '') {
+      throw new ParseException('Unexpected characters near "' + value.slice(context.i) + '".');
+    }
+    return result;
+  };
+
+  Inline.dump = function(value, exceptionOnInvalidType, objectEncoder) {
+    var ref, result, type;
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectEncoder == null) {
+      objectEncoder = null;
+    }
+    if (value == null) {
+      return 'null';
+    }
+    type = typeof value;
+    if (type === 'object') {
+      if (value instanceof Date) {
+        return value.toISOString();
+      } else if (objectEncoder != null) {
+        result = objectEncoder(value);
+        if (typeof result === 'string' || (result != null)) {
+          return result;
+        }
+      }
+      return this.dumpObject(value);
+    }
+    if (type === 'boolean') {
+      return (value ? 'true' : 'false');
+    }
+    if (Utils.isDigits(value)) {
+      return (type === 'string' ? "'" + value + "'" : String(parseInt(value)));
+    }
+    if (Utils.isNumeric(value)) {
+      return (type === 'string' ? "'" + value + "'" : String(parseFloat(value)));
+    }
+    if (type === 'number') {
+      return (value === Infinity ? '.Inf' : (value === -Infinity ? '-.Inf' : (isNaN(value) ? '.NaN' : value)));
+    }
+    if (Escaper.requiresDoubleQuoting(value)) {
+      return Escaper.escapeWithDoubleQuotes(value);
+    }
+    if (Escaper.requiresSingleQuoting(value)) {
+      return Escaper.escapeWithSingleQuotes(value);
+    }
+    if ('' === value) {
+      return '""';
+    }
+    if (Utils.PATTERN_DATE.test(value)) {
+      return "'" + value + "'";
+    }
+    if ((ref = value.toLowerCase()) === 'null' || ref === '~' || ref === 'true' || ref === 'false') {
+      return "'" + value + "'";
+    }
+    return value;
+  };
+
+  Inline.dumpObject = function(value, exceptionOnInvalidType, objectSupport) {
+    var j, key, len1, output, val;
+    if (objectSupport == null) {
+      objectSupport = null;
+    }
+    if (value instanceof Array) {
+      output = [];
+      for (j = 0, len1 = value.length; j < len1; j++) {
+        val = value[j];
+        output.push(this.dump(val));
+      }
+      return '[' + output.join(', ') + ']';
+    } else {
+      output = [];
+      for (key in value) {
+        val = value[key];
+        output.push(this.dump(key) + ': ' + this.dump(val));
+      }
+      return '{' + output.join(', ') + '}';
+    }
+  };
+
+  Inline.parseScalar = function(scalar, delimiters, stringDelimiters, context, evaluate) {
+    var i, joinedDelimiters, match, output, pattern, ref, ref1, strpos, tmp;
+    if (delimiters == null) {
+      delimiters = null;
+    }
+    if (stringDelimiters == null) {
+      stringDelimiters = ['"', "'"];
+    }
+    if (context == null) {
+      context = null;
+    }
+    if (evaluate == null) {
+      evaluate = true;
+    }
+    if (context == null) {
+      context = {
+        exceptionOnInvalidType: this.settings.exceptionOnInvalidType,
+        objectDecoder: this.settings.objectDecoder,
+        i: 0
+      };
+    }
+    i = context.i;
+    if (ref = scalar.charAt(i), indexOf.call(stringDelimiters, ref) >= 0) {
+      output = this.parseQuotedScalar(scalar, context);
+      i = context.i;
+      if (delimiters != null) {
+        tmp = Utils.ltrim(scalar.slice(i), ' ');
+        if (!(ref1 = tmp.charAt(0), indexOf.call(delimiters, ref1) >= 0)) {
+          throw new ParseException('Unexpected characters (' + scalar.slice(i) + ').');
+        }
+      }
+    } else {
+      if (!delimiters) {
+        output = scalar.slice(i);
+        i += output.length;
+        strpos = output.indexOf(' #');
+        if (strpos !== -1) {
+          output = Utils.rtrim(output.slice(0, strpos));
+        }
+      } else {
+        joinedDelimiters = delimiters.join('|');
+        pattern = this.PATTERN_SCALAR_BY_DELIMITERS[joinedDelimiters];
+        if (pattern == null) {
+          pattern = new Pattern('^(.+?)(' + joinedDelimiters + ')');
+          this.PATTERN_SCALAR_BY_DELIMITERS[joinedDelimiters] = pattern;
+        }
+        if (match = pattern.exec(scalar.slice(i))) {
+          output = match[1];
+          i += output.length;
+        } else {
+          throw new ParseException('Malformed inline YAML string (' + scalar + ').');
+        }
+      }
+      if (evaluate) {
+        output = this.evaluateScalar(output, context);
+      }
+    }
+    context.i = i;
+    return output;
+  };
+
+  Inline.parseQuotedScalar = function(scalar, context) {
+    var i, match, output;
+    i = context.i;
+    if (!(match = this.PATTERN_QUOTED_SCALAR.exec(scalar.slice(i)))) {
+      throw new ParseException('Malformed inline YAML string (' + scalar.slice(i) + ').');
+    }
+    output = match[0].substr(1, match[0].length - 2);
+    if ('"' === scalar.charAt(i)) {
+      output = Unescaper.unescapeDoubleQuotedString(output);
+    } else {
+      output = Unescaper.unescapeSingleQuotedString(output);
+    }
+    i += match[0].length;
+    context.i = i;
+    return output;
+  };
+
+  Inline.parseSequence = function(sequence, context) {
+    var e, error, i, isQuoted, len, output, ref, value;
+    output = [];
+    len = sequence.length;
+    i = context.i;
+    i += 1;
+    while (i < len) {
+      context.i = i;
+      switch (sequence.charAt(i)) {
+        case '[':
+          output.push(this.parseSequence(sequence, context));
+          i = context.i;
+          break;
+        case '{':
+          output.push(this.parseMapping(sequence, context));
+          i = context.i;
+          break;
+        case ']':
+          return output;
+        case ',':
+        case ' ':
+        case "\n":
+          break;
+        default:
+          isQuoted = ((ref = sequence.charAt(i)) === '"' || ref === "'");
+          value = this.parseScalar(sequence, [',', ']'], ['"', "'"], context);
+          i = context.i;
+          if (!isQuoted && typeof value === 'string' && (value.indexOf(': ') !== -1 || value.indexOf(":\n") !== -1)) {
+            try {
+              value = this.parseMapping('{' + value + '}');
+            } catch (error) {
+              e = error;
+            }
+          }
+          output.push(value);
+          --i;
+      }
+      ++i;
+    }
+    throw new ParseException('Malformed inline YAML string ' + sequence);
+  };
+
+  Inline.parseMapping = function(mapping, context) {
+    var done, i, key, len, output, shouldContinueWhileLoop, value;
+    output = {};
+    len = mapping.length;
+    i = context.i;
+    i += 1;
+    shouldContinueWhileLoop = false;
+    while (i < len) {
+      context.i = i;
+      switch (mapping.charAt(i)) {
+        case ' ':
+        case ',':
+        case "\n":
+          ++i;
+          context.i = i;
+          shouldContinueWhileLoop = true;
+          break;
+        case '}':
+          return output;
+      }
+      if (shouldContinueWhileLoop) {
+        shouldContinueWhileLoop = false;
+        continue;
+      }
+      key = this.parseScalar(mapping, [':', ' ', "\n"], ['"', "'"], context, false);
+      i = context.i;
+      done = false;
+      while (i < len) {
+        context.i = i;
+        switch (mapping.charAt(i)) {
+          case '[':
+            value = this.parseSequence(mapping, context);
+            i = context.i;
+            if (output[key] === void 0) {
+              output[key] = value;
+            }
+            done = true;
+            break;
+          case '{':
+            value = this.parseMapping(mapping, context);
+            i = context.i;
+            if (output[key] === void 0) {
+              output[key] = value;
+            }
+            done = true;
+            break;
+          case ':':
+          case ' ':
+          case "\n":
+            break;
+          default:
+            value = this.parseScalar(mapping, [',', '}'], ['"', "'"], context);
+            i = context.i;
+            if (output[key] === void 0) {
+              output[key] = value;
+            }
+            done = true;
+            --i;
+        }
+        ++i;
+        if (done) {
+          break;
+        }
+      }
+    }
+    throw new ParseException('Malformed inline YAML string ' + mapping);
+  };
+
+  Inline.evaluateScalar = function(scalar, context) {
+    var cast, date, exceptionOnInvalidType, firstChar, firstSpace, firstWord, objectDecoder, raw, scalarLower, subValue, trimmedScalar;
+    scalar = Utils.trim(scalar);
+    scalarLower = scalar.toLowerCase();
+    switch (scalarLower) {
+      case 'null':
+      case '':
+      case '~':
+        return null;
+      case 'true':
+        return true;
+      case 'false':
+        return false;
+      case '.inf':
+        return Infinity;
+      case '.nan':
+        return NaN;
+      case '-.inf':
+        return Infinity;
+      default:
+        firstChar = scalarLower.charAt(0);
+        switch (firstChar) {
+          case '!':
+            firstSpace = scalar.indexOf(' ');
+            if (firstSpace === -1) {
+              firstWord = scalarLower;
+            } else {
+              firstWord = scalarLower.slice(0, firstSpace);
+            }
+            switch (firstWord) {
+              case '!':
+                if (firstSpace !== -1) {
+                  return parseInt(this.parseScalar(scalar.slice(2)));
+                }
+                return null;
+              case '!str':
+                return Utils.ltrim(scalar.slice(4));
+              case '!!str':
+                return Utils.ltrim(scalar.slice(5));
+              case '!!int':
+                return parseInt(this.parseScalar(scalar.slice(5)));
+              case '!!bool':
+                return Utils.parseBoolean(this.parseScalar(scalar.slice(6)), false);
+              case '!!float':
+                return parseFloat(this.parseScalar(scalar.slice(7)));
+              case '!!timestamp':
+                return Utils.stringToDate(Utils.ltrim(scalar.slice(11)));
+              default:
+                if (context == null) {
+                  context = {
+                    exceptionOnInvalidType: this.settings.exceptionOnInvalidType,
+                    objectDecoder: this.settings.objectDecoder,
+                    i: 0
+                  };
+                }
+                objectDecoder = context.objectDecoder, exceptionOnInvalidType = context.exceptionOnInvalidType;
+                if (objectDecoder) {
+                  trimmedScalar = Utils.rtrim(scalar);
+                  firstSpace = trimmedScalar.indexOf(' ');
+                  if (firstSpace === -1) {
+                    return objectDecoder(trimmedScalar, null);
+                  } else {
+                    subValue = Utils.ltrim(trimmedScalar.slice(firstSpace + 1));
+                    if (!(subValue.length > 0)) {
+                      subValue = null;
+                    }
+                    return objectDecoder(trimmedScalar.slice(0, firstSpace), subValue);
+                  }
+                }
+                if (exceptionOnInvalidType) {
+                  throw new ParseException('Custom object support when parsing a YAML file has been disabled.');
+                }
+                return null;
+            }
+            break;
+          case '0':
+            if ('0x' === scalar.slice(0, 2)) {
+              return Utils.hexDec(scalar);
+            } else if (Utils.isDigits(scalar)) {
+              return Utils.octDec(scalar);
+            } else if (Utils.isNumeric(scalar)) {
+              return parseFloat(scalar);
+            } else {
+              return scalar;
+            }
+            break;
+          case '+':
+            if (Utils.isDigits(scalar)) {
+              raw = scalar;
+              cast = parseInt(raw);
+              if (raw === String(cast)) {
+                return cast;
+              } else {
+                return raw;
+              }
+            } else if (Utils.isNumeric(scalar)) {
+              return parseFloat(scalar);
+            } else if (this.PATTERN_THOUSAND_NUMERIC_SCALAR.test(scalar)) {
+              return parseFloat(scalar.replace(',', ''));
+            }
+            return scalar;
+          case '-':
+            if (Utils.isDigits(scalar.slice(1))) {
+              if ('0' === scalar.charAt(1)) {
+                return -Utils.octDec(scalar.slice(1));
+              } else {
+                raw = scalar.slice(1);
+                cast = parseInt(raw);
+                if (raw === String(cast)) {
+                  return -cast;
+                } else {
+                  return -raw;
+                }
+              }
+            } else if (Utils.isNumeric(scalar)) {
+              return parseFloat(scalar);
+            } else if (this.PATTERN_THOUSAND_NUMERIC_SCALAR.test(scalar)) {
+              return parseFloat(scalar.replace(',', ''));
+            }
+            return scalar;
+          default:
+            if (date = Utils.stringToDate(scalar)) {
+              return date;
+            } else if (Utils.isNumeric(scalar)) {
+              return parseFloat(scalar);
+            } else if (this.PATTERN_THOUSAND_NUMERIC_SCALAR.test(scalar)) {
+              return parseFloat(scalar.replace(',', ''));
+            }
+            return scalar;
+        }
+    }
+  };
+
+  return Inline;
+
+})();
+
+module.exports = Inline;
+
+
+},{"./Escaper":2,"./Exception/DumpException":3,"./Exception/ParseException":4,"./Pattern":7,"./Unescaper":8,"./Utils":9}],6:[function(require,module,exports){
+var Inline, ParseException, Parser, Pattern, Utils;
+
+Inline = require('./Inline');
+
+Pattern = require('./Pattern');
+
+Utils = require('./Utils');
+
+ParseException = require('./Exception/ParseException');
+
+Parser = (function() {
+  Parser.prototype.PATTERN_FOLDED_SCALAR_ALL = new Pattern('^(?:(?<type>![^\\|>]*)\\s+)?(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$');
+
+  Parser.prototype.PATTERN_FOLDED_SCALAR_END = new Pattern('(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$');
+
+  Parser.prototype.PATTERN_SEQUENCE_ITEM = new Pattern('^\\-((?<leadspaces>\\s+)(?<value>.+?))?\\s*$');
+
+  Parser.prototype.PATTERN_ANCHOR_VALUE = new Pattern('^&(?<ref>[^ ]+) *(?<value>.*)');
+
+  Parser.prototype.PATTERN_COMPACT_NOTATION = new Pattern('^(?<key>' + Inline.REGEX_QUOTED_STRING + '|[^ \'"\\{\\[].*?) *\\:(\\s+(?<value>.+?))?\\s*$');
+
+  Parser.prototype.PATTERN_MAPPING_ITEM = new Pattern('^(?<key>' + Inline.REGEX_QUOTED_STRING + '|[^ \'"\\[\\{].*?) *\\:(\\s+(?<value>.+?))?\\s*$');
+
+  Parser.prototype.PATTERN_DECIMAL = new Pattern('\\d+');
+
+  Parser.prototype.PATTERN_INDENT_SPACES = new Pattern('^ +');
+
+  Parser.prototype.PATTERN_TRAILING_LINES = new Pattern('(\n*)$');
+
+  Parser.prototype.PATTERN_YAML_HEADER = new Pattern('^\\%YAML[: ][\\d\\.]+.*\n');
+
+  Parser.prototype.PATTERN_LEADING_COMMENTS = new Pattern('^(\\#.*?\n)+');
+
+  Parser.prototype.PATTERN_DOCUMENT_MARKER_START = new Pattern('^\\-\\-\\-.*?\n');
+
+  Parser.prototype.PATTERN_DOCUMENT_MARKER_END = new Pattern('^\\.\\.\\.\\s*$');
+
+  Parser.prototype.PATTERN_FOLDED_SCALAR_BY_INDENTATION = {};
+
+  Parser.prototype.CONTEXT_NONE = 0;
+
+  Parser.prototype.CONTEXT_SEQUENCE = 1;
+
+  Parser.prototype.CONTEXT_MAPPING = 2;
+
+  function Parser(offset) {
+    this.offset = offset != null ? offset : 0;
+    this.lines = [];
+    this.currentLineNb = -1;
+    this.currentLine = '';
+    this.refs = {};
+  }
+
+  Parser.prototype.parse = function(value, exceptionOnInvalidType, objectDecoder) {
+    var alias, allowOverwrite, block, c, context, data, e, error, error1, error2, first, i, indent, isRef, j, k, key, l, lastKey, len, len1, len2, len3, lineCount, m, matches, mergeNode, n, name, parsed, parsedItem, parser, ref, ref1, ref2, refName, refValue, val, values;
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectDecoder == null) {
+      objectDecoder = null;
+    }
+    this.currentLineNb = -1;
+    this.currentLine = '';
+    this.lines = this.cleanup(value).split("\n");
+    data = null;
+    context = this.CONTEXT_NONE;
+    allowOverwrite = false;
+    while (this.moveToNextLine()) {
+      if (this.isCurrentLineEmpty()) {
+        continue;
+      }
+      if ("\t" === this.currentLine[0]) {
+        throw new ParseException('A YAML file cannot contain tabs as indentation.', this.getRealCurrentLineNb() + 1, this.currentLine);
+      }
+      isRef = mergeNode = false;
+      if (values = this.PATTERN_SEQUENCE_ITEM.exec(this.currentLine)) {
+        if (this.CONTEXT_MAPPING === context) {
+          throw new ParseException('You cannot define a sequence item when in a mapping');
+        }
+        context = this.CONTEXT_SEQUENCE;
+        if (data == null) {
+          data = [];
+        }
+        if ((values.value != null) && (matches = this.PATTERN_ANCHOR_VALUE.exec(values.value))) {
+          isRef = matches.ref;
+          values.value = matches.value;
+        }
+        if (!(values.value != null) || '' === Utils.trim(values.value, ' ') || Utils.ltrim(values.value, ' ').indexOf('#') === 0) {
+          if (this.currentLineNb < this.lines.length - 1 && !this.isNextLineUnIndentedCollection()) {
+            c = this.getRealCurrentLineNb() + 1;
+            parser = new Parser(c);
+            parser.refs = this.refs;
+            data.push(parser.parse(this.getNextEmbedBlock(null, true), exceptionOnInvalidType, objectDecoder));
+          } else {
+            data.push(null);
+          }
+        } else {
+          if (((ref = values.leadspaces) != null ? ref.length : void 0) && (matches = this.PATTERN_COMPACT_NOTATION.exec(values.value))) {
+            c = this.getRealCurrentLineNb();
+            parser = new Parser(c);
+            parser.refs = this.refs;
+            block = values.value;
+            indent = this.getCurrentLineIndentation();
+            if (this.isNextLineIndented(false)) {
+              block += "\n" + this.getNextEmbedBlock(indent + values.leadspaces.length + 1, true);
+            }
+            data.push(parser.parse(block, exceptionOnInvalidType, objectDecoder));
+          } else {
+            data.push(this.parseValue(values.value, exceptionOnInvalidType, objectDecoder));
+          }
+        }
+      } else if ((values = this.PATTERN_MAPPING_ITEM.exec(this.currentLine)) && values.key.indexOf(' #') === -1) {
+        if (this.CONTEXT_SEQUENCE === context) {
+          throw new ParseException('You cannot define a mapping item when in a sequence');
+        }
+        context = this.CONTEXT_MAPPING;
+        if (data == null) {
+          data = {};
+        }
+        Inline.configure(exceptionOnInvalidType, objectDecoder);
+        try {
+          key = Inline.parseScalar(values.key);
+        } catch (error) {
+          e = error;
+          e.parsedLine = this.getRealCurrentLineNb() + 1;
+          e.snippet = this.currentLine;
+          throw e;
+        }
+        if ('<<' === key) {
+          mergeNode = true;
+          allowOverwrite = true;
+          if (((ref1 = values.value) != null ? ref1.indexOf('*') : void 0) === 0) {
+            refName = values.value.slice(1);
+            if (this.refs[refName] == null) {
+              throw new ParseException('Reference "' + refName + '" does not exist.', this.getRealCurrentLineNb() + 1, this.currentLine);
+            }
+            refValue = this.refs[refName];
+            if (typeof refValue !== 'object') {
+              throw new ParseException('YAML merge keys used with a scalar value instead of an object.', this.getRealCurrentLineNb() + 1, this.currentLine);
+            }
+            if (refValue instanceof Array) {
+              for (i = j = 0, len = refValue.length; j < len; i = ++j) {
+                value = refValue[i];
+                if (data[name = String(i)] == null) {
+                  data[name] = value;
+                }
+              }
+            } else {
+              for (key in refValue) {
+                value = refValue[key];
+                if (data[key] == null) {
+                  data[key] = value;
+                }
+              }
+            }
+          } else {
+            if ((values.value != null) && values.value !== '') {
+              value = values.value;
+            } else {
+              value = this.getNextEmbedBlock();
+            }
+            c = this.getRealCurrentLineNb() + 1;
+            parser = new Parser(c);
+            parser.refs = this.refs;
+            parsed = parser.parse(value, exceptionOnInvalidType);
+            if (typeof parsed !== 'object') {
+              throw new ParseException('YAML merge keys used with a scalar value instead of an object.', this.getRealCurrentLineNb() + 1, this.currentLine);
+            }
+            if (parsed instanceof Array) {
+              for (l = 0, len1 = parsed.length; l < len1; l++) {
+                parsedItem = parsed[l];
+                if (typeof parsedItem !== 'object') {
+                  throw new ParseException('Merge items must be objects.', this.getRealCurrentLineNb() + 1, parsedItem);
+                }
+                if (parsedItem instanceof Array) {
+                  for (i = m = 0, len2 = parsedItem.length; m < len2; i = ++m) {
+                    value = parsedItem[i];
+                    k = String(i);
+                    if (!data.hasOwnProperty(k)) {
+                      data[k] = value;
+                    }
+                  }
+                } else {
+                  for (key in parsedItem) {
+                    value = parsedItem[key];
+                    if (!data.hasOwnProperty(key)) {
+                      data[key] = value;
+                    }
+                  }
+                }
+              }
+            } else {
+              for (key in parsed) {
+                value = parsed[key];
+                if (!data.hasOwnProperty(key)) {
+                  data[key] = value;
+                }
+              }
+            }
+          }
+        } else if ((values.value != null) && (matches = this.PATTERN_ANCHOR_VALUE.exec(values.value))) {
+          isRef = matches.ref;
+          values.value = matches.value;
+        }
+        if (mergeNode) {
+
+        } else if (!(values.value != null) || '' === Utils.trim(values.value, ' ') || Utils.ltrim(values.value, ' ').indexOf('#') === 0) {
+          if (!(this.isNextLineIndented()) && !(this.isNextLineUnIndentedCollection())) {
+            if (allowOverwrite || data[key] === void 0) {
+              data[key] = null;
+            }
+          } else {
+            c = this.getRealCurrentLineNb() + 1;
+            parser = new Parser(c);
+            parser.refs = this.refs;
+            val = parser.parse(this.getNextEmbedBlock(), exceptionOnInvalidType, objectDecoder);
+            if (allowOverwrite || data[key] === void 0) {
+              data[key] = val;
+            }
+          }
+        } else {
+          val = this.parseValue(values.value, exceptionOnInvalidType, objectDecoder);
+          if (allowOverwrite || data[key] === void 0) {
+            data[key] = val;
+          }
+        }
+      } else {
+        lineCount = this.lines.length;
+        if (1 === lineCount || (2 === lineCount && Utils.isEmpty(this.lines[1]))) {
+          try {
+            value = Inline.parse(this.lines[0], exceptionOnInvalidType, objectDecoder);
+          } catch (error1) {
+            e = error1;
+            e.parsedLine = this.getRealCurrentLineNb() + 1;
+            e.snippet = this.currentLine;
+            throw e;
+          }
+          if (typeof value === 'object') {
+            if (value instanceof Array) {
+              first = value[0];
+            } else {
+              for (key in value) {
+                first = value[key];
+                break;
+              }
+            }
+            if (typeof first === 'string' && first.indexOf('*') === 0) {
+              data = [];
+              for (n = 0, len3 = value.length; n < len3; n++) {
+                alias = value[n];
+                data.push(this.refs[alias.slice(1)]);
+              }
+              value = data;
+            }
+          }
+          return value;
+        } else if ((ref2 = Utils.ltrim(value).charAt(0)) === '[' || ref2 === '{') {
+          try {
+            return Inline.parse(value, exceptionOnInvalidType, objectDecoder);
+          } catch (error2) {
+            e = error2;
+            e.parsedLine = this.getRealCurrentLineNb() + 1;
+            e.snippet = this.currentLine;
+            throw e;
+          }
+        }
+        throw new ParseException('Unable to parse.', this.getRealCurrentLineNb() + 1, this.currentLine);
+      }
+      if (isRef) {
+        if (data instanceof Array) {
+          this.refs[isRef] = data[data.length - 1];
+        } else {
+          lastKey = null;
+          for (key in data) {
+            lastKey = key;
+          }
+          this.refs[isRef] = data[lastKey];
+        }
+      }
+    }
+    if (Utils.isEmpty(data)) {
+      return null;
+    } else {
+      return data;
+    }
+  };
+
+  Parser.prototype.getRealCurrentLineNb = function() {
+    return this.currentLineNb + this.offset;
+  };
+
+  Parser.prototype.getCurrentLineIndentation = function() {
+    return this.currentLine.length - Utils.ltrim(this.currentLine, ' ').length;
+  };
+
+  Parser.prototype.getNextEmbedBlock = function(indentation, includeUnindentedCollection) {
+    var data, indent, isItUnindentedCollection, newIndent, removeComments, removeCommentsPattern, unindentedEmbedBlock;
+    if (indentation == null) {
+      indentation = null;
+    }
+    if (includeUnindentedCollection == null) {
+      includeUnindentedCollection = false;
+    }
+    this.moveToNextLine();
+    if (indentation == null) {
+      newIndent = this.getCurrentLineIndentation();
+      unindentedEmbedBlock = this.isStringUnIndentedCollectionItem(this.currentLine);
+      if (!(this.isCurrentLineEmpty()) && 0 === newIndent && !unindentedEmbedBlock) {
+        throw new ParseException('Indentation problem.', this.getRealCurrentLineNb() + 1, this.currentLine);
+      }
+    } else {
+      newIndent = indentation;
+    }
+    data = [this.currentLine.slice(newIndent)];
+    if (!includeUnindentedCollection) {
+      isItUnindentedCollection = this.isStringUnIndentedCollectionItem(this.currentLine);
+    }
+    removeCommentsPattern = this.PATTERN_FOLDED_SCALAR_END;
+    removeComments = !removeCommentsPattern.test(this.currentLine);
+    while (this.moveToNextLine()) {
+      indent = this.getCurrentLineIndentation();
+      if (indent === newIndent) {
+        removeComments = !removeCommentsPattern.test(this.currentLine);
+      }
+      if (isItUnindentedCollection && !this.isStringUnIndentedCollectionItem(this.currentLine) && indent === newIndent) {
+        this.moveToPreviousLine();
+        break;
+      }
+      if (this.isCurrentLineBlank()) {
+        data.push(this.currentLine.slice(newIndent));
+        continue;
+      }
+      if (removeComments && this.isCurrentLineComment()) {
+        if (indent === newIndent) {
+          continue;
+        }
+      }
+      if (indent >= newIndent) {
+        data.push(this.currentLine.slice(newIndent));
+      } else if (Utils.ltrim(this.currentLine).charAt(0) === '#') {
+
+      } else if (0 === indent) {
+        this.moveToPreviousLine();
+        break;
+      } else {
+        throw new ParseException('Indentation problem.', this.getRealCurrentLineNb() + 1, this.currentLine);
+      }
+    }
+    return data.join("\n");
+  };
+
+  Parser.prototype.moveToNextLine = function() {
+    if (this.currentLineNb >= this.lines.length - 1) {
+      return false;
+    }
+    this.currentLine = this.lines[++this.currentLineNb];
+    return true;
+  };
+
+  Parser.prototype.moveToPreviousLine = function() {
+    this.currentLine = this.lines[--this.currentLineNb];
+  };
+
+  Parser.prototype.parseValue = function(value, exceptionOnInvalidType, objectDecoder) {
+    var e, error, error1, foldedIndent, matches, modifiers, pos, ref, ref1, val;
+    if (0 === value.indexOf('*')) {
+      pos = value.indexOf('#');
+      if (pos !== -1) {
+        value = value.substr(1, pos - 2);
+      } else {
+        value = value.slice(1);
+      }
+      if (this.refs[value] === void 0) {
+        throw new ParseException('Reference "' + value + '" does not exist.', this.currentLine);
+      }
+      return this.refs[value];
+    }
+    if (matches = this.PATTERN_FOLDED_SCALAR_ALL.exec(value)) {
+      modifiers = (ref = matches.modifiers) != null ? ref : '';
+      foldedIndent = Math.abs(parseInt(modifiers));
+      if (isNaN(foldedIndent)) {
+        foldedIndent = 0;
+      }
+      val = this.parseFoldedScalar(matches.separator, this.PATTERN_DECIMAL.replace(modifiers, ''), foldedIndent);
+      if (matches.type != null) {
+        Inline.configure(exceptionOnInvalidType, objectDecoder);
+        return Inline.parseScalar(matches.type + ' ' + val);
+      } else {
+        return val;
+      }
+    }
+    try {
+      return Inline.parse(value, exceptionOnInvalidType, objectDecoder);
+    } catch (error) {
+      e = error;
+      if (((ref1 = value.charAt(0)) === '[' || ref1 === '{') && e instanceof ParseException && this.isNextLineIndented()) {
+        value += "\n" + this.getNextEmbedBlock();
+        try {
+          return Inline.parse(value, exceptionOnInvalidType, objectDecoder);
+        } catch (error1) {
+          e = error1;
+          e.parsedLine = this.getRealCurrentLineNb() + 1;
+          e.snippet = this.currentLine;
+          throw e;
+        }
+      } else {
+        e.parsedLine = this.getRealCurrentLineNb() + 1;
+        e.snippet = this.currentLine;
+        throw e;
+      }
+    }
+  };
+
+  Parser.prototype.parseFoldedScalar = function(separator, indicator, indentation) {
+    var isCurrentLineBlank, j, len, line, matches, newText, notEOF, pattern, ref, text;
+    if (indicator == null) {
+      indicator = '';
+    }
+    if (indentation == null) {
+      indentation = 0;
+    }
+    notEOF = this.moveToNextLine();
+    if (!notEOF) {
+      return '';
+    }
+    isCurrentLineBlank = this.isCurrentLineBlank();
+    text = '';
+    while (notEOF && isCurrentLineBlank) {
+      if (notEOF = this.moveToNextLine()) {
+        text += "\n";
+        isCurrentLineBlank = this.isCurrentLineBlank();
+      }
+    }
+    if (0 === indentation) {
+      if (matches = this.PATTERN_INDENT_SPACES.exec(this.currentLine)) {
+        indentation = matches[0].length;
+      }
+    }
+    if (indentation > 0) {
+      pattern = this.PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation];
+      if (pattern == null) {
+        pattern = new Pattern('^ {' + indentation + '}(.*)$');
+        Parser.prototype.PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation] = pattern;
+      }
+      while (notEOF && (isCurrentLineBlank || (matches = pattern.exec(this.currentLine)))) {
+        if (isCurrentLineBlank) {
+          text += this.currentLine.slice(indentation);
+        } else {
+          text += matches[1];
+        }
+        if (notEOF = this.moveToNextLine()) {
+          text += "\n";
+          isCurrentLineBlank = this.isCurrentLineBlank();
+        }
+      }
+    } else if (notEOF) {
+      text += "\n";
+    }
+    if (notEOF) {
+      this.moveToPreviousLine();
+    }
+    if ('>' === separator) {
+      newText = '';
+      ref = text.split("\n");
+      for (j = 0, len = ref.length; j < len; j++) {
+        line = ref[j];
+        if (line.length === 0 || line.charAt(0) === ' ') {
+          newText = Utils.rtrim(newText, ' ') + line + "\n";
+        } else {
+          newText += line + ' ';
+        }
+      }
+      text = newText;
+    }
+    if ('+' !== indicator) {
+      text = Utils.rtrim(text);
+    }
+    if ('' === indicator) {
+      text = this.PATTERN_TRAILING_LINES.replace(text, "\n");
+    } else if ('-' === indicator) {
+      text = this.PATTERN_TRAILING_LINES.replace(text, '');
+    }
+    return text;
+  };
+
+  Parser.prototype.isNextLineIndented = function(ignoreComments) {
+    var EOF, currentIndentation, ret;
+    if (ignoreComments == null) {
+      ignoreComments = true;
+    }
+    currentIndentation = this.getCurrentLineIndentation();
+    EOF = !this.moveToNextLine();
+    if (ignoreComments) {
+      while (!EOF && this.isCurrentLineEmpty()) {
+        EOF = !this.moveToNextLine();
+      }
+    } else {
+      while (!EOF && this.isCurrentLineBlank()) {
+        EOF = !this.moveToNextLine();
+      }
+    }
+    if (EOF) {
+      return false;
+    }
+    ret = false;
+    if (this.getCurrentLineIndentation() > currentIndentation) {
+      ret = true;
+    }
+    this.moveToPreviousLine();
+    return ret;
+  };
+
+  Parser.prototype.isCurrentLineEmpty = function() {
+    var trimmedLine;
+    trimmedLine = Utils.trim(this.currentLine, ' ');
+    return trimmedLine.length === 0 || trimmedLine.charAt(0) === '#';
+  };
+
+  Parser.prototype.isCurrentLineBlank = function() {
+    return '' === Utils.trim(this.currentLine, ' ');
+  };
+
+  Parser.prototype.isCurrentLineComment = function() {
+    var ltrimmedLine;
+    ltrimmedLine = Utils.ltrim(this.currentLine, ' ');
+    return ltrimmedLine.charAt(0) === '#';
+  };
+
+  Parser.prototype.cleanup = function(value) {
+    var count, i, indent, j, l, len, len1, line, lines, ref, ref1, ref2, smallestIndent, trimmedValue;
+    if (value.indexOf("\r") !== -1) {
+      value = value.split("\r\n").join("\n").split("\r").join("\n");
+    }
+    count = 0;
+    ref = this.PATTERN_YAML_HEADER.replaceAll(value, ''), value = ref[0], count = ref[1];
+    this.offset += count;
+    ref1 = this.PATTERN_LEADING_COMMENTS.replaceAll(value, '', 1), trimmedValue = ref1[0], count = ref1[1];
+    if (count === 1) {
+      this.offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n");
+      value = trimmedValue;
+    }
+    ref2 = this.PATTERN_DOCUMENT_MARKER_START.replaceAll(value, '', 1), trimmedValue = ref2[0], count = ref2[1];
+    if (count === 1) {
+      this.offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n");
+      value = trimmedValue;
+      value = this.PATTERN_DOCUMENT_MARKER_END.replace(value, '');
+    }
+    lines = value.split("\n");
+    smallestIndent = -1;
+    for (j = 0, len = lines.length; j < len; j++) {
+      line = lines[j];
+      if (Utils.trim(line, ' ').length === 0) {
+        continue;
+      }
+      indent = line.length - Utils.ltrim(line).length;
+      if (smallestIndent === -1 || indent < smallestIndent) {
+        smallestIndent = indent;
+      }
+    }
+    if (smallestIndent > 0) {
+      for (i = l = 0, len1 = lines.length; l < len1; i = ++l) {
+        line = lines[i];
+        lines[i] = line.slice(smallestIndent);
+      }
+      value = lines.join("\n");
+    }
+    return value;
+  };
+
+  Parser.prototype.isNextLineUnIndentedCollection = function(currentIndentation) {
+    var notEOF, ret;
+    if (currentIndentation == null) {
+      currentIndentation = null;
+    }
+    if (currentIndentation == null) {
+      currentIndentation = this.getCurrentLineIndentation();
+    }
+    notEOF = this.moveToNextLine();
+    while (notEOF && this.isCurrentLineEmpty()) {
+      notEOF = this.moveToNextLine();
+    }
+    if (false === notEOF) {
+      return false;
+    }
+    ret = false;
+    if (this.getCurrentLineIndentation() === currentIndentation && this.isStringUnIndentedCollectionItem(this.currentLine)) {
+      ret = true;
+    }
+    this.moveToPreviousLine();
+    return ret;
+  };
+
+  Parser.prototype.isStringUnIndentedCollectionItem = function() {
+    return this.currentLine === '-' || this.currentLine.slice(0, 2) === '- ';
+  };
+
+  return Parser;
+
+})();
+
+module.exports = Parser;
+
+
+},{"./Exception/ParseException":4,"./Inline":5,"./Pattern":7,"./Utils":9}],7:[function(require,module,exports){
+var Pattern;
+
+Pattern = (function() {
+  Pattern.prototype.regex = null;
+
+  Pattern.prototype.rawRegex = null;
+
+  Pattern.prototype.cleanedRegex = null;
+
+  Pattern.prototype.mapping = null;
+
+  function Pattern(rawRegex, modifiers) {
+    var _char, capturingBracketNumber, cleanedRegex, i, len, mapping, name, part, subChar;
+    if (modifiers == null) {
+      modifiers = '';
+    }
+    cleanedRegex = '';
+    len = rawRegex.length;
+    mapping = null;
+    capturingBracketNumber = 0;
+    i = 0;
+    while (i < len) {
+      _char = rawRegex.charAt(i);
+      if (_char === '\\') {
+        cleanedRegex += rawRegex.slice(i, +(i + 1) + 1 || 9e9);
+        i++;
+      } else if (_char === '(') {
+        if (i < len - 2) {
+          part = rawRegex.slice(i, +(i + 2) + 1 || 9e9);
+          if (part === '(?:') {
+            i += 2;
+            cleanedRegex += part;
+          } else if (part === '(?<') {
+            capturingBracketNumber++;
+            i += 2;
+            name = '';
+            while (i + 1 < len) {
+              subChar = rawRegex.charAt(i + 1);
+              if (subChar === '>') {
+                cleanedRegex += '(';
+                i++;
+                if (name.length > 0) {
+                  if (mapping == null) {
+                    mapping = {};
+                  }
+                  mapping[name] = capturingBracketNumber;
+                }
+                break;
+              } else {
+                name += subChar;
+              }
+              i++;
+            }
+          } else {
+            cleanedRegex += _char;
+            capturingBracketNumber++;
+          }
+        } else {
+          cleanedRegex += _char;
+        }
+      } else {
+        cleanedRegex += _char;
+      }
+      i++;
+    }
+    this.rawRegex = rawRegex;
+    this.cleanedRegex = cleanedRegex;
+    this.regex = new RegExp(this.cleanedRegex, 'g' + modifiers.replace('g', ''));
+    this.mapping = mapping;
+  }
+
+  Pattern.prototype.exec = function(str) {
+    var index, matches, name, ref;
+    this.regex.lastIndex = 0;
+    matches = this.regex.exec(str);
+    if (matches == null) {
+      return null;
+    }
+    if (this.mapping != null) {
+      ref = this.mapping;
+      for (name in ref) {
+        index = ref[name];
+        matches[name] = matches[index];
+      }
+    }
+    return matches;
+  };
+
+  Pattern.prototype.test = function(str) {
+    this.regex.lastIndex = 0;
+    return this.regex.test(str);
+  };
+
+  Pattern.prototype.replace = function(str, replacement) {
+    this.regex.lastIndex = 0;
+    return str.replace(this.regex, replacement);
+  };
+
+  Pattern.prototype.replaceAll = function(str, replacement, limit) {
+    var count;
+    if (limit == null) {
+      limit = 0;
+    }
+    this.regex.lastIndex = 0;
+    count = 0;
+    while (this.regex.test(str) && (limit === 0 || count < limit)) {
+      this.regex.lastIndex = 0;
+      str = str.replace(this.regex, '');
+      count++;
+    }
+    return [str, count];
+  };
+
+  return Pattern;
+
+})();
+
+module.exports = Pattern;
+
+
+},{}],8:[function(require,module,exports){
+var Pattern, Unescaper, Utils;
+
+Utils = require('./Utils');
+
+Pattern = require('./Pattern');
+
+Unescaper = (function() {
+  function Unescaper() {}
+
+  Unescaper.PATTERN_ESCAPED_CHARACTER = new Pattern('\\\\([0abt\tnvfre "\\/\\\\N_LP]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})');
+
+  Unescaper.unescapeSingleQuotedString = function(value) {
+    return value.replace(/\'\'/g, '\'');
+  };
+
+  Unescaper.unescapeDoubleQuotedString = function(value) {
+    if (this._unescapeCallback == null) {
+      this._unescapeCallback = (function(_this) {
+        return function(str) {
+          return _this.unescapeCharacter(str);
+        };
+      })(this);
+    }
+    return this.PATTERN_ESCAPED_CHARACTER.replace(value, this._unescapeCallback);
+  };
+
+  Unescaper.unescapeCharacter = function(value) {
+    var ch;
+    ch = String.fromCharCode;
+    switch (value.charAt(1)) {
+      case '0':
+        return ch(0);
+      case 'a':
+        return ch(7);
+      case 'b':
+        return ch(8);
+      case 't':
+        return "\t";
+      case "\t":
+        return "\t";
+      case 'n':
+        return "\n";
+      case 'v':
+        return ch(11);
+      case 'f':
+        return ch(12);
+      case 'r':
+        return ch(13);
+      case 'e':
+        return ch(27);
+      case ' ':
+        return ' ';
+      case '"':
+        return '"';
+      case '/':
+        return '/';
+      case '\\':
+        return '\\';
+      case 'N':
+        return ch(0x0085);
+      case '_':
+        return ch(0x00A0);
+      case 'L':
+        return ch(0x2028);
+      case 'P':
+        return ch(0x2029);
+      case 'x':
+        return Utils.utf8chr(Utils.hexDec(value.substr(2, 2)));
+      case 'u':
+        return Utils.utf8chr(Utils.hexDec(value.substr(2, 4)));
+      case 'U':
+        return Utils.utf8chr(Utils.hexDec(value.substr(2, 8)));
+      default:
+        return '';
+    }
+  };
+
+  return Unescaper;
+
+})();
+
+module.exports = Unescaper;
+
+
+},{"./Pattern":7,"./Utils":9}],9:[function(require,module,exports){
+var Pattern, Utils;
+
+Pattern = require('./Pattern');
+
+Utils = (function() {
+  function Utils() {}
+
+  Utils.REGEX_LEFT_TRIM_BY_CHAR = {};
+
+  Utils.REGEX_RIGHT_TRIM_BY_CHAR = {};
+
+  Utils.REGEX_SPACES = /\s+/g;
+
+  Utils.REGEX_DIGITS = /^\d+$/;
+
+  Utils.REGEX_OCTAL = /[^0-7]/gi;
+
+  Utils.REGEX_HEXADECIMAL = /[^a-f0-9]/gi;
+
+  Utils.PATTERN_DATE = new Pattern('^' + '(?<year>[0-9][0-9][0-9][0-9])' + '-(?<month>[0-9][0-9]?)' + '-(?<day>[0-9][0-9]?)' + '(?:(?:[Tt]|[ \t]+)' + '(?<hour>[0-9][0-9]?)' + ':(?<minute>[0-9][0-9])' + ':(?<second>[0-9][0-9])' + '(?:\.(?<fraction>[0-9]*))?' + '(?:[ \t]*(?<tz>Z|(?<tz_sign>[-+])(?<tz_hour>[0-9][0-9]?)' + '(?::(?<tz_minute>[0-9][0-9]))?))?)?' + '$', 'i');
+
+  Utils.LOCAL_TIMEZONE_OFFSET = new Date().getTimezoneOffset() * 60 * 1000;
+
+  Utils.trim = function(str, _char) {
+    var regexLeft, regexRight;
+    if (_char == null) {
+      _char = '\\s';
+    }
+    return str.trim();
+    regexLeft = this.REGEX_LEFT_TRIM_BY_CHAR[_char];
+    if (regexLeft == null) {
+      this.REGEX_LEFT_TRIM_BY_CHAR[_char] = regexLeft = new RegExp('^' + _char + '' + _char + '*');
+    }
+    regexLeft.lastIndex = 0;
+    regexRight = this.REGEX_RIGHT_TRIM_BY_CHAR[_char];
+    if (regexRight == null) {
+      this.REGEX_RIGHT_TRIM_BY_CHAR[_char] = regexRight = new RegExp(_char + '' + _char + '*$');
+    }
+    regexRight.lastIndex = 0;
+    return str.replace(regexLeft, '').replace(regexRight, '');
+  };
+
+  Utils.ltrim = function(str, _char) {
+    var regexLeft;
+    if (_char == null) {
+      _char = '\\s';
+    }
+    regexLeft = this.REGEX_LEFT_TRIM_BY_CHAR[_char];
+    if (regexLeft == null) {
+      this.REGEX_LEFT_TRIM_BY_CHAR[_char] = regexLeft = new RegExp('^' + _char + '' + _char + '*');
+    }
+    regexLeft.lastIndex = 0;
+    return str.replace(regexLeft, '');
+  };
+
+  Utils.rtrim = function(str, _char) {
+    var regexRight;
+    if (_char == null) {
+      _char = '\\s';
+    }
+    regexRight = this.REGEX_RIGHT_TRIM_BY_CHAR[_char];
+    if (regexRight == null) {
+      this.REGEX_RIGHT_TRIM_BY_CHAR[_char] = regexRight = new RegExp(_char + '' + _char + '*$');
+    }
+    regexRight.lastIndex = 0;
+    return str.replace(regexRight, '');
+  };
+
+  Utils.isEmpty = function(value) {
+    return !value || value === '' || value === '0' || (value instanceof Array && value.length === 0);
+  };
+
+  Utils.subStrCount = function(string, subString, start, length) {
+    var c, i, j, len, ref, sublen;
+    c = 0;
+    string = '' + string;
+    subString = '' + subString;
+    if (start != null) {
+      string = string.slice(start);
+    }
+    if (length != null) {
+      string = string.slice(0, length);
+    }
+    len = string.length;
+    sublen = subString.length;
+    for (i = j = 0, ref = len; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
+      if (subString === string.slice(i, sublen)) {
+        c++;
+        i += sublen - 1;
+      }
+    }
+    return c;
+  };
+
+  Utils.isDigits = function(input) {
+    this.REGEX_DIGITS.lastIndex = 0;
+    return this.REGEX_DIGITS.test(input);
+  };
+
+  Utils.octDec = function(input) {
+    this.REGEX_OCTAL.lastIndex = 0;
+    return parseInt((input + '').replace(this.REGEX_OCTAL, ''), 8);
+  };
+
+  Utils.hexDec = function(input) {
+    this.REGEX_HEXADECIMAL.lastIndex = 0;
+    input = this.trim(input);
+    if ((input + '').slice(0, 2) === '0x') {
+      input = (input + '').slice(2);
+    }
+    return parseInt((input + '').replace(this.REGEX_HEXADECIMAL, ''), 16);
+  };
+
+  Utils.utf8chr = function(c) {
+    var ch;
+    ch = String.fromCharCode;
+    if (0x80 > (c %= 0x200000)) {
+      return ch(c);
+    }
+    if (0x800 > c) {
+      return ch(0xC0 | c >> 6) + ch(0x80 | c & 0x3F);
+    }
+    if (0x10000 > c) {
+      return ch(0xE0 | c >> 12) + ch(0x80 | c >> 6 & 0x3F) + ch(0x80 | c & 0x3F);
+    }
+    return ch(0xF0 | c >> 18) + ch(0x80 | c >> 12 & 0x3F) + ch(0x80 | c >> 6 & 0x3F) + ch(0x80 | c & 0x3F);
+  };
+
+  Utils.parseBoolean = function(input, strict) {
+    var lowerInput;
+    if (strict == null) {
+      strict = true;
+    }
+    if (typeof input === 'string') {
+      lowerInput = input.toLowerCase();
+      if (!strict) {
+        if (lowerInput === 'no') {
+          return false;
+        }
+      }
+      if (lowerInput === '0') {
+        return false;
+      }
+      if (lowerInput === 'false') {
+        return false;
+      }
+      if (lowerInput === '') {
+        return false;
+      }
+      return true;
+    }
+    return !!input;
+  };
+
+  Utils.isNumeric = function(input) {
+    this.REGEX_SPACES.lastIndex = 0;
+    return typeof input === 'number' || typeof input === 'string' && !isNaN(input) && input.replace(this.REGEX_SPACES, '') !== '';
+  };
+
+  Utils.stringToDate = function(str) {
+    var date, day, fraction, hour, info, minute, month, second, tz_hour, tz_minute, tz_offset, year;
+    if (!(str != null ? str.length : void 0)) {
+      return null;
+    }
+    info = this.PATTERN_DATE.exec(str);
+    if (!info) {
+      return null;
+    }
+    year = parseInt(info.year, 10);
+    month = parseInt(info.month, 10) - 1;
+    day = parseInt(info.day, 10);
+    if (info.hour == null) {
+      date = new Date(Date.UTC(year, month, day));
+      return date;
+    }
+    hour = parseInt(info.hour, 10);
+    minute = parseInt(info.minute, 10);
+    second = parseInt(info.second, 10);
+    if (info.fraction != null) {
+      fraction = info.fraction.slice(0, 3);
+      while (fraction.length < 3) {
+        fraction += '0';
+      }
+      fraction = parseInt(fraction, 10);
+    } else {
+      fraction = 0;
+    }
+    if (info.tz != null) {
+      tz_hour = parseInt(info.tz_hour, 10);
+      if (info.tz_minute != null) {
+        tz_minute = parseInt(info.tz_minute, 10);
+      } else {
+        tz_minute = 0;
+      }
+      tz_offset = (tz_hour * 60 + tz_minute) * 60000;
+      if ('-' === info.tz_sign) {
+        tz_offset *= -1;
+      }
+    }
+    date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction));
+    if (tz_offset) {
+      date.setTime(date.getTime() + tz_offset);
+    }
+    return date;
+  };
+
+  Utils.strRepeat = function(str, number) {
+    var i, res;
+    res = '';
+    i = 0;
+    while (i < number) {
+      res += str;
+      i++;
+    }
+    return res;
+  };
+
+  Utils.getStringFromFile = function(path, callback) {
+    var data, fs, j, len1, name, ref, req, xhr;
+    if (callback == null) {
+      callback = null;
+    }
+    xhr = null;
+    if (typeof window !== "undefined" && window !== null) {
+      if (window.XMLHttpRequest) {
+        xhr = new XMLHttpRequest();
+      } else if (window.ActiveXObject) {
+        ref = ["Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.3.0", "Msxml2.XMLHTTP", "Microsoft.XMLHTTP"];
+        for (j = 0, len1 = ref.length; j < len1; j++) {
+          name = ref[j];
+          try {
+            xhr = new ActiveXObject(name);
+          } catch (undefined) {}
+        }
+      }
+    }
+    if (xhr != null) {
+      if (callback != null) {
+        xhr.onreadystatechange = function() {
+          if (xhr.readyState === 4) {
+            if (xhr.status === 200 || xhr.status === 0) {
+              return callback(xhr.responseText);
+            } else {
+              return callback(null);
+            }
+          }
+        };
+        xhr.open('GET', path, true);
+        return xhr.send(null);
+      } else {
+        xhr.open('GET', path, false);
+        xhr.send(null);
+        if (xhr.status === 200 || xhr.status === 0) {
+          return xhr.responseText;
+        }
+        return null;
+      }
+    } else {
+      req = require;
+      fs = req('fs');
+      if (callback != null) {
+        return fs.readFile(path, function(err, data) {
+          if (err) {
+            return callback(null);
+          } else {
+            return callback(String(data));
+          }
+        });
+      } else {
+        data = fs.readFileSync(path);
+        if (data != null) {
+          return String(data);
+        }
+        return null;
+      }
+    }
+  };
+
+  return Utils;
+
+})();
+
+module.exports = Utils;
+
+
+},{"./Pattern":7}],10:[function(require,module,exports){
+var Dumper, Parser, Utils, Yaml;
+
+Parser = require('./Parser');
+
+Dumper = require('./Dumper');
+
+Utils = require('./Utils');
+
+Yaml = (function() {
+  function Yaml() {}
+
+  Yaml.parse = function(input, exceptionOnInvalidType, objectDecoder) {
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectDecoder == null) {
+      objectDecoder = null;
+    }
+    return new Parser().parse(input, exceptionOnInvalidType, objectDecoder);
+  };
+
+  Yaml.parseFile = function(path, callback, exceptionOnInvalidType, objectDecoder) {
+    var input;
+    if (callback == null) {
+      callback = null;
+    }
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectDecoder == null) {
+      objectDecoder = null;
+    }
+    if (callback != null) {
+      return Utils.getStringFromFile(path, (function(_this) {
+        return function(input) {
+          var result;
+          result = null;
+          if (input != null) {
+            result = _this.parse(input, exceptionOnInvalidType, objectDecoder);
+          }
+          callback(result);
+        };
+      })(this));
+    } else {
+      input = Utils.getStringFromFile(path);
+      if (input != null) {
+        return this.parse(input, exceptionOnInvalidType, objectDecoder);
+      }
+      return null;
+    }
+  };
+
+  Yaml.dump = function(input, inline, indent, exceptionOnInvalidType, objectEncoder) {
+    var yaml;
+    if (inline == null) {
+      inline = 2;
+    }
+    if (indent == null) {
+      indent = 4;
+    }
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectEncoder == null) {
+      objectEncoder = null;
+    }
+    yaml = new Dumper();
+    yaml.indentation = indent;
+    return yaml.dump(input, inline, 0, exceptionOnInvalidType, objectEncoder);
+  };
+
+  Yaml.register = function() {
+    var require_handler;
+    require_handler = function(module, filename) {
+      return module.exports = YAML.parseFile(filename);
+    };
+    if ((typeof require !== "undefined" && require !== null ? require.extensions : void 0) != null) {
+      require.extensions['.yml'] = require_handler;
+      return require.extensions['.yaml'] = require_handler;
+    }
+  };
+
+  Yaml.stringify = function(input, inline, indent, exceptionOnInvalidType, objectEncoder) {
+    return this.dump(input, inline, indent, exceptionOnInvalidType, objectEncoder);
+  };
+
+  Yaml.load = function(path, callback, exceptionOnInvalidType, objectDecoder) {
+    return this.parseFile(path, callback, exceptionOnInvalidType, objectDecoder);
+  };
+
+  return Yaml;
+
+})();
+
+if (typeof window !== "undefined" && window !== null) {
+  window.YAML = Yaml;
+}
+
+if (typeof window === "undefined" || window === null) {
+  this.YAML = Yaml;
+}
+
+module.exports = Yaml;
+
+
+},{"./Dumper":1,"./Parser":6,"./Utils":9}]},{},[10]);
diff --git a/src/main/webapp/scripts/lib/yamljs/dist/yaml.legacy.js b/src/main/webapp/scripts/lib/yamljs/dist/yaml.legacy.js
new file mode 100755
index 0000000000000000000000000000000000000000..88c1a2891f6dbb519bdee1bee2f0f9dba924a0d1
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/dist/yaml.legacy.js
@@ -0,0 +1,2087 @@
+/*
+Copyright (c) 2010 Jeremy Faivre
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+(function(){
+/**
+ * Exception class thrown when an error occurs during parsing.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @api
+ */
+ 
+/**
+ * Constructor.
+ *
+ * @param string	message	The error message
+ * @param integer   parsedLine The line where the error occurred
+ * @param integer   snippet	The snippet of code near the problem
+ * @param string	parsedFile The file name where the error occurred
+ */
+
+var YamlParseException = function(message, parsedLine, snippet, parsedFile){
+
+		this.rawMessage = message;
+		this.parsedLine = (parsedLine !== undefined) ? parsedLine : -1;
+		this.snippet = (snippet !== undefined) ? snippet : null;
+		this.parsedFile = (parsedFile !== undefined) ? parsedFile : null;
+
+		this.updateRepr();
+		
+		this.message = message;
+
+};
+YamlParseException.prototype =
+{
+
+	name: 'YamlParseException',
+	message: null,
+	
+	parsedFile: null,
+	parsedLine: -1,
+	snippet: null,
+	rawMessage: null,
+
+	isDefined: function(input)
+	{
+		return input != undefined && input != null;
+	},
+
+	/**
+	* Gets the snippet of code near the error.
+	*
+	* @return string The snippet of code
+	*/
+	getSnippet: function()
+	{
+		return this.snippet;
+	},
+
+	/**
+	* Sets the snippet of code near the error.
+	*
+	* @param string snippet The code snippet
+	*/
+	setSnippet: function(snippet)
+	{
+		this.snippet = snippet;
+
+		this.updateRepr();
+	},
+
+	/**
+	* Gets the filename where the error occurred.
+	*
+	* This method returns null if a string is parsed.
+	*
+	* @return string The filename
+	*/
+	getParsedFile: function()
+	{
+		return this.parsedFile;
+	},
+
+	/**
+	* Sets the filename where the error occurred.
+	*
+	* @param string parsedFile The filename
+	*/
+	setParsedFile: function(parsedFile)
+	{
+		this.parsedFile = parsedFile;
+
+		this.updateRepr();
+	},
+
+	/**
+	* Gets the line where the error occurred.
+	*
+	* @return integer The file line
+	*/
+	getParsedLine: function()
+	{
+		return this.parsedLine;
+	},
+
+	/**
+	* Sets the line where the error occurred.
+	*
+	* @param integer parsedLine The file line
+	*/
+	setParsedLine: function(parsedLine)
+	{
+		this.parsedLine = parsedLine;
+
+		this.updateRepr();
+	},
+
+	updateRepr: function()
+	{
+		this.message = this.rawMessage;
+
+		var dot = false;
+		if ('.' === this.message.charAt(this.message.length - 1)) {
+			this.message = this.message.substring(0, this.message.length - 1);
+			dot = true;
+		}
+
+		if (null !== this.parsedFile) {
+			this.message += ' in ' + JSON.stringify(this.parsedFile);
+		}
+
+		if (this.parsedLine >= 0) {
+			this.message += ' at line ' + this.parsedLine;
+		}
+
+		if (this.snippet) {
+			this.message += ' (near "' + this.snippet + '")';
+		}
+
+		if (dot) {
+			this.message += '.';
+		}
+	}
+}
+/**
+ * Yaml offers convenience methods to parse and dump YAML.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ *
+ * @api
+ */
+
+var YamlRunningUnderNode = false;
+var Yaml = function(){};
+Yaml.prototype =
+{
+
+	/**
+	 * Parses YAML into a JS representation.
+	 *
+	 * The parse method, when supplied with a YAML stream (file),
+	 * will do its best to convert YAML in a file into a JS representation.
+	 *
+	 *	Usage:
+	 *	<code>
+	 *	 obj = yaml.parseFile('config.yml');
+	 *	</code>
+	 *
+	 * @param string input Path of YAML file
+	 *
+	 * @return array The YAML converted to a JS representation
+	 *
+	 * @throws YamlParseException If the YAML is not valid
+	 */
+	parseFile: function(file /* String */, callback /* Function */)
+	{
+		if ( callback == null )
+		{
+			var input = this.getFileContents(file);
+			var ret = null;
+			try
+			{
+				ret = this.parse(input);
+			}
+			catch ( e )
+			{
+				if ( e instanceof YamlParseException ) {
+					e.setParsedFile(file);
+				}
+				throw e;
+			}
+			return ret;
+		}
+		
+		this.getFileContents(file, function(data)
+		{
+			callback(new Yaml().parse(data));
+		});
+	},
+
+	/**
+	 * Parses YAML into a JS representation.
+	 *
+	 * The parse method, when supplied with a YAML stream (string),
+	 * will do its best to convert YAML into a JS representation.
+	 *
+	 *	Usage:
+	 *	<code>
+	 *	 obj = yaml.parse(...);
+	 *	</code>
+	 *
+	 * @param string input string containing YAML
+	 *
+	 * @return array The YAML converted to a JS representation
+	 *
+	 * @throws YamlParseException If the YAML is not valid
+	 */
+	parse: function(input /* String */)
+	{
+		var yaml = new YamlParser();
+
+		return yaml.parse(input);
+	},
+
+	/**
+	 * Dumps a JS representation to a YAML string.
+	 *
+	 * The dump method, when supplied with an array, will do its best
+	 * to convert the array into friendly YAML.
+	 *
+	 * @param array	 array JS representation
+	 * @param integer inline The level where you switch to inline YAML
+	 *
+	 * @return string A YAML string representing the original JS representation
+    *
+    * @api
+    */
+	dump: function(array, inline, spaces)
+	{
+		if ( inline == null ) inline = 2;
+
+		var yaml = new YamlDumper();
+		if (spaces) {
+		    yaml.numSpacesForIndentation = spaces;
+		}
+
+		return yaml.dump(array, inline);
+	},
+	
+	getXHR: function()
+	{
+		if ( window.XMLHttpRequest )
+			return new XMLHttpRequest();
+		 
+		if ( window.ActiveXObject )
+		{
+			var names = [
+			"Msxml2.XMLHTTP.6.0",
+			"Msxml2.XMLHTTP.3.0",
+			"Msxml2.XMLHTTP",
+			"Microsoft.XMLHTTP"
+			];
+			
+			for ( var i = 0; i < 4; i++ )
+			{
+				try{ return new ActiveXObject(names[i]); }
+				catch(e){}
+			}
+		}
+		return null;
+	},
+	
+	getFileContents: function(file, callback)
+	{
+	    if ( YamlRunningUnderNode )
+	    {
+	        var fs = require('fs');
+	        if ( callback == null )
+	        {
+	            var data = fs.readFileSync(file);
+	            if (data == null) return null;
+	            return ''+data;
+	        }
+	        else
+	        {
+	            fs.readFile(file, function(err, data)
+	            {
+	                if (err)
+	                    callback(null);
+	                else
+	                    callback(data);
+	            });
+	        }
+	    }
+	    else
+	    {
+    		var request = this.getXHR();
+		
+    		// Sync
+    		if ( callback == null )
+    		{
+    			request.open('GET', file, false);                  
+    			request.send(null);
+
+    			if ( request.status == 200 || request.status == 0 )
+    				return request.responseText;
+			
+    			return null;
+    		}
+		
+    		// Async
+    		request.onreadystatechange = function()
+    		{
+    			if ( request.readyState == 4 )
+    				if ( request.status == 200 || request.status == 0 )
+    					callback(request.responseText);
+    				else
+    					callback(null);
+    		};
+    		request.open('GET', file, true);                  
+    		request.send(null);
+	    }
+	}
+};
+
+var YAML =
+{
+	/*
+	 * @param integer inline The level where you switch to inline YAML
+	 */
+	 
+	stringify: function(input, inline, spaces)
+	{
+		return new Yaml().dump(input, inline, spaces);
+	},
+	
+	parse: function(input)
+	{
+		return new Yaml().parse(input);
+	},
+	
+	load: function(file, callback)
+	{
+		return new Yaml().parseFile(file, callback);
+	}
+};
+
+// Handle node.js case
+if (typeof exports !== 'undefined') {
+    if (typeof module !== 'undefined' && module.exports) {
+        exports = module.exports = YAML;
+        YamlRunningUnderNode = true;
+        
+        // Add require handler
+        (function () {
+            var require_handler = function (module, filename) {
+                // fill in result
+                module.exports = YAML.load(filename);
+            };
+
+            // register require extensions only if we're on node.js
+            // hack for browserify
+            if ( undefined !== require.extensions ) {
+                require.extensions['.yml'] = require_handler;
+                require.extensions['.yaml'] = require_handler;
+            }
+        }());
+    }
+}
+
+// Handle browser case
+if ( typeof(window) != "undefined" )
+{
+    window.YAML = YAML;
+}
+
+/**
+ * YamlInline implements a YAML parser/dumper for the YAML inline syntax.
+ */
+var YamlInline = function(){};
+YamlInline.prototype =
+{
+	i: null,
+	
+	/**
+	 * Convert a YAML string to a JS object.
+	 *
+	 * @param string value A YAML string
+	 *
+	 * @return object A JS object representing the YAML string
+	 */
+	parse: function(value)
+	{
+		var result = null;
+		value = this.trim(value);
+
+		if ( 0 == value.length )
+		{
+			return '';
+		}
+
+		switch ( value.charAt(0) )
+		{
+			case '[':
+				result = this.parseSequence(value);
+				break;
+			case '{':
+				result = this.parseMapping(value);
+				break;
+			default:
+				result = this.parseScalar(value);
+		}
+
+		// some comment can end the scalar
+		if ( value.substr(this.i+1).replace(/^\s*#.*$/, '') != '' ) {
+		    console.log("oups "+value.substr(this.i+1));
+			throw new YamlParseException('Unexpected characters near "'+value.substr(this.i)+'".');
+		}
+
+		return result;
+	},
+
+	/**
+	 * Dumps a given JS variable to a YAML string.
+	 *
+	 * @param mixed value The JS variable to convert
+	 *
+	 * @return string The YAML string representing the JS object
+	 */
+	dump: function(value)
+	{
+		if ( undefined == value || null == value )
+			return 'null';	
+		if ( value instanceof Date)
+			return value.toISOString();
+		if ( typeof(value) == 'object')
+			return this.dumpObject(value);
+		if ( typeof(value) == 'boolean' )
+			return value ? 'true' : 'false';
+		if ( /^\d+$/.test(value) )
+			return typeof(value) == 'string' ? "'"+value+"'" : parseInt(value);
+		if ( this.isNumeric(value) )
+			return typeof(value) == 'string' ? "'"+value+"'" : parseFloat(value);
+		if ( typeof(value) == 'number' )
+			return value == Infinity ? '.Inf' : ( value == -Infinity ? '-.Inf' : ( isNaN(value) ? '.NAN' : value ) );
+		var yaml = new YamlEscaper();
+		if ( yaml.requiresDoubleQuoting(value) )
+			return yaml.escapeWithDoubleQuotes(value);
+		if ( yaml.requiresSingleQuoting(value) )
+			return yaml.escapeWithSingleQuotes(value);
+		if ( '' == value )
+			return '""';
+		if ( this.getTimestampRegex().test(value) )
+			return "'"+value+"'";
+		if ( this.inArray(value.toLowerCase(), ['null','~','true','false']) )
+			return "'"+value+"'";
+		// default
+			return value;
+	},
+
+	/**
+	 * Dumps a JS object to a YAML string.
+	 *
+	 * @param object value The JS array to dump
+	 *
+	 * @return string The YAML string representing the JS object
+	 */
+	dumpObject: function(value)
+	{
+		var keys = this.getKeys(value);
+		var output = null;
+		var i;
+		var len = keys.length;
+
+		// array
+		if ( value instanceof Array )
+			/*( 1 == len && '0' == keys[0] )
+			||
+			( len > 1 && this.reduceArray(keys, function(v,w){return Math.floor(v+w);}, 0) == len * (len - 1) / 2) )*/
+		{
+			output = [];
+			for ( i = 0; i < len; i++ )
+			{
+				output.push(this.dump(value[keys[i]]));
+			}
+
+			return '['+output.join(', ')+']';
+		}
+
+		// mapping
+		output = [];
+		for ( i = 0; i < len; i++ )
+		{
+			output.push(this.dump(keys[i])+': '+this.dump(value[keys[i]]));
+		}
+
+		return '{ '+output.join(', ')+' }';
+	},
+
+	/**
+	 * Parses a scalar to a YAML string.
+	 *
+	 * @param scalar scalar
+	 * @param string delimiters
+	 * @param object stringDelimiters
+	 * @param integer i
+	 * @param boolean evaluate
+	 *
+	 * @return string A YAML string
+	 *
+	 * @throws YamlParseException When malformed inline YAML string is parsed
+	 */
+	parseScalar: function(scalar, delimiters, stringDelimiters, i, evaluate)
+	{
+		if ( delimiters == undefined ) delimiters = null;
+		if ( stringDelimiters == undefined ) stringDelimiters = ['"', "'"];
+		if ( i == undefined ) i = 0;
+		if ( evaluate == undefined ) evaluate = true;
+		
+		var output = null;
+		var pos = null;
+		var matches = null;
+		
+		if ( this.inArray(scalar[i], stringDelimiters) )
+		{
+			// quoted scalar
+			output = this.parseQuotedScalar(scalar, i);
+			i = this.i;
+			if (null !== delimiters) {
+				var tmp = scalar.substr(i).replace(/^\s+/, '');
+				if (!this.inArray(tmp.charAt(0), delimiters)) {
+					throw new YamlParseException('Unexpected characters ('+scalar.substr(i)+').');
+				}
+			}
+		}
+		else
+		{
+			// "normal" string
+			if ( !delimiters )
+			{
+				output = (scalar+'').substring(i);
+				
+				i += output.length;
+
+				// remove comments
+				pos = output.indexOf(' #');
+				if ( pos != -1 )
+				{
+					output = output.substr(0, pos).replace(/\s+$/g,'');
+				}
+			}
+			else if ( matches = new RegExp('^(.+?)('+delimiters.join('|')+')').exec((scalar+'').substring(i)) )
+			{
+				output = matches[1];
+				i += output.length;
+			}
+			else
+			{
+				throw new YamlParseException('Malformed inline YAML string ('+scalar+').');
+			}
+			output = evaluate ? this.evaluateScalar(output) : output;
+		}
+
+		this.i = i;
+		
+		return output;
+	},
+
+	/**
+	 * Parses a quoted scalar to YAML.
+	 *
+	 * @param string	scalar
+	 * @param integer i
+	 *
+	 * @return string A YAML string
+	 *
+	 * @throws YamlParseException When malformed inline YAML string is parsed
+	 */
+	parseQuotedScalar: function(scalar, i)
+	{
+		var matches = null;
+		//var item = /^(.*?)['"]\s*(?:[,:]|[}\]]\s*,)/.exec((scalar+'').substring(i))[1];
+		
+		if ( !(matches = new RegExp('^'+YamlInline.REGEX_QUOTED_STRING).exec((scalar+'').substring(i))) )
+		{
+			throw new YamlParseException('Malformed inline YAML string ('+(scalar+'').substring(i)+').');
+		}
+
+		var output = matches[0].substr(1, matches[0].length - 2);
+		
+		var unescaper = new YamlUnescaper();
+
+		if ( '"' == (scalar+'').charAt(i) )
+		{
+			output = unescaper.unescapeDoubleQuotedString(output);
+		}
+		else
+		{
+			output = unescaper.unescapeSingleQuotedString(output);
+		}
+
+		i += matches[0].length;
+
+		this.i = i;
+		return output;
+	},
+
+	/**
+	 * Parses a sequence to a YAML string.
+	 *
+	 * @param string sequence
+	 * @param integer i
+	 *
+	 * @return string A YAML string
+	 *
+	 * @throws YamlParseException When malformed inline YAML string is parsed
+	 */
+	parseSequence: function(sequence, i)
+	{
+		if ( i == undefined ) i = 0;
+		
+		var output = [];
+		var len = sequence.length;
+		i += 1;
+
+		// [foo, bar, ...]
+		while ( i < len )
+		{
+			switch ( sequence.charAt(i) )
+			{
+				case '[':
+					// nested sequence
+					output.push(this.parseSequence(sequence, i));
+					i = this.i;
+					break;
+				case '{':
+					// nested mapping
+					output.push(this.parseMapping(sequence, i));
+					i = this.i;
+					break;
+				case ']':
+					this.i = i;
+					return output;
+				case ',':
+				case ' ':
+					break;
+				default:
+					var isQuoted = this.inArray(sequence.charAt(i), ['"', "'"]);
+					var value = this.parseScalar(sequence, [',', ']'], ['"', "'"], i);
+					i = this.i;
+					
+					if ( !isQuoted && (value+'').indexOf(': ') != -1 )
+					{
+						// embedded mapping?
+						try
+						{
+							value = this.parseMapping('{'+value+'}');
+						}
+						catch ( e )
+						{
+							if ( !(e instanceof YamlParseException ) ) throw e;
+							// no, it's not
+						}
+					}
+
+					output.push(value);
+
+					i--;
+			}
+
+			i++;
+		}
+
+		throw new YamlParseException('Malformed inline YAML string "'+sequence+'"');
+	},
+
+	/**
+	 * Parses a mapping to a YAML string.
+	 *
+	 * @param string mapping
+	 * @param integer i
+	 *
+	 * @return string A YAML string
+	 *
+	 * @throws YamlParseException When malformed inline YAML string is parsed
+	 */
+	parseMapping: function(mapping, i)
+	{
+		if ( i == undefined ) i = 0;
+		var output = {};
+		var len = mapping.length;
+		i += 1;
+		var done = false;
+		var doContinue = false;
+
+		// {foo: bar, bar:foo, ...}
+		while ( i < len )
+		{
+			doContinue = false;
+			
+			switch ( mapping.charAt(i) )
+			{
+				case ' ':
+				case ',':
+					i++;
+					doContinue = true;
+					break;
+				case '}':
+					this.i = i;
+					return output;
+			}
+			
+			if ( doContinue ) continue;
+
+			// key
+			var key = this.parseScalar(mapping, [':', ' '], ['"', "'"], i, false);
+			i = this.i;
+
+			// value
+			done = false;
+			while ( i < len )
+			{
+				switch ( mapping.charAt(i) )
+				{
+					case '[':
+						// nested sequence
+						output[key] = this.parseSequence(mapping, i);
+						i = this.i;
+						done = true;
+						break;
+					case '{':
+						// nested mapping
+						output[key] = this.parseMapping(mapping, i);
+						i = this.i;
+						done = true;
+						break;
+					case ':':
+					case ' ':
+						break;
+					default:
+						output[key] = this.parseScalar(mapping, [',', '}'], ['"', "'"], i);
+						i = this.i;
+						done = true;
+						i--;
+				}
+
+				++i;
+
+				if ( done )
+				{
+					doContinue = true;
+					break;
+				}
+			}
+			
+			if ( doContinue ) continue;
+		}
+
+		throw new YamlParseException('Malformed inline YAML string "'+mapping+'"');
+	},
+
+	/**
+	 * Evaluates scalars and replaces magic values.
+	 *
+	 * @param string scalar
+	 *
+	 * @return string A YAML string
+	 */
+	evaluateScalar: function(scalar)
+	{
+		scalar = this.trim(scalar);
+		
+		var raw = null;
+		var cast = null;
+
+		if (	( 'null' == scalar.toLowerCase() ) ||
+				( '' == scalar ) ||
+				( '~' == scalar ) )
+			return null;
+		if ( (scalar+'').indexOf('!str ') == 0 )
+			return (''+scalar).substring(5);
+		if ( (scalar+'').indexOf('! ') == 0 )
+			return parseInt(this.parseScalar((scalar+'').substr(2)));
+		if ( /^\d+$/.test(scalar) )
+		{
+			raw = scalar;
+			cast = parseInt(scalar);
+			return '0' == scalar.charAt(0) ? this.octdec(scalar) : (( ''+raw == ''+cast ) ? cast : raw);
+		}
+		if ( 'true' == (scalar+'').toLowerCase() )
+			return true;
+		if ( 'false' == (scalar+'').toLowerCase() )
+			return false;
+		if ( this.isNumeric(scalar) )
+			return '0x' == (scalar+'').substr(0, 2) ? this.hexdec(scalar) : parseFloat(scalar);
+		if ( scalar.toLowerCase() == '.inf' )
+			return Infinity;
+		if ( scalar.toLowerCase() == '.nan' )
+			return NaN;
+		if ( scalar.toLowerCase() == '-.inf' )
+			return -Infinity;
+		if ( /^(-|\+)?[0-9,]+(\.[0-9]+)?$/.test(scalar) )
+			return parseFloat(scalar.split(',').join(''));
+		if ( this.getTimestampRegex().test(scalar) )
+			return new Date(this.strtotime(scalar));
+		//else
+			return ''+scalar;
+	},
+
+	/**
+	 * Gets a regex that matches an unix timestamp
+	 *
+	 * @return string The regular expression
+	 */
+	getTimestampRegex: function()
+	{
+		return new RegExp('^'+
+		'([0-9][0-9][0-9][0-9])'+
+		'-([0-9][0-9]?)'+
+		'-([0-9][0-9]?)'+
+		'(?:(?:[Tt]|[ \t]+)'+
+		'([0-9][0-9]?)'+
+		':([0-9][0-9])'+
+		':([0-9][0-9])'+
+		'(?:\.([0-9]*))?'+
+		'(?:[ \t]*(Z|([-+])([0-9][0-9]?)'+
+		'(?::([0-9][0-9]))?))?)?'+
+		'$','gi');
+	},
+	
+	trim: function(str /* String */)
+	{
+		return (str+'').replace(/^\s+/,'').replace(/\s+$/,'');
+	},
+	
+	isNumeric: function(input)
+	{
+		return (input - 0) == input && input.length > 0 && input.replace(/\s+/g,'') != '';
+	},
+	
+	inArray: function(key, tab)
+	{
+		var i;
+		var len = tab.length;
+		for ( i = 0; i < len; i++ )
+		{
+			if ( key == tab[i] ) return true;
+		}
+		return false;
+	},
+	
+	getKeys: function(tab)
+	{
+		var ret = [];
+		
+		for ( var name in tab )
+		{
+			if ( tab.hasOwnProperty(name) )
+			{
+				ret.push(name);
+			}
+		}
+		
+		return ret;
+	},
+	
+	/*reduceArray: function(tab, fun)
+	{
+		var len = tab.length;
+		if (typeof fun != "function")
+			throw new YamlParseException("fun is not a function");
+		
+		// no value to return if no initial value and an empty array
+		if (len == 0 && arguments.length == 1)
+			throw new YamlParseException("empty array");
+		
+		var i = 0;
+		if (arguments.length >= 2)
+		{
+			var rv = arguments[1];
+		}
+		else
+		{
+			do
+			{
+				if (i in tab)
+				{
+					rv = tab[i++];
+					break;
+				}
+		
+				// if array contains no values, no initial value to return
+				if (++i >= len)
+					throw new YamlParseException("no initial value to return");
+			}
+			while (true);
+		}
+
+		for (; i < len; i++)
+		{
+			if (i in tab)
+				rv = fun.call(null, rv, tab[i], i, tab);
+		}
+
+		return rv;
+	},*/
+	
+	octdec: function(input)
+	{
+	    return parseInt((input+'').replace(/[^0-7]/gi, ''), 8);
+	},
+	
+	hexdec: function(input)
+	{
+		input = this.trim(input);
+		if ( (input+'').substr(0, 2) == '0x' ) input = (input+'').substring(2);
+	    return parseInt((input+'').replace(/[^a-f0-9]/gi, ''), 16);
+	},
+	
+	/**
+	 * @see http://phpjs.org/functions/strtotime
+	 * @note we need timestamp with msecs so /1000 removed
+	 * @note original contained binary | 0 (wtf?!) everywhere, which messes everything up 
+	 */
+	strtotime: function (h,b){var f,c,g,k,d="";h=(h+"").replace(/\s{2,}|^\s|\s$/g," ").replace(/[\t\r\n]/g,"");if(h==="now"){return b===null||isNaN(b)?new Date().getTime()||0:b||0}else{if(!isNaN(d=Date.parse(h))){return d||0}else{if(b){b=new Date(b)}else{b=new Date()}}}h=h.toLowerCase();var e={day:{sun:0,mon:1,tue:2,wed:3,thu:4,fri:5,sat:6},mon:["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"]};var a=function(i){var o=(i[2]&&i[2]==="ago");var n=(n=i[0]==="last"?-1:1)*(o?-1:1);switch(i[0]){case"last":case"next":switch(i[1].substring(0,3)){case"yea":b.setFullYear(b.getFullYear()+n);break;case"wee":b.setDate(b.getDate()+(n*7));break;case"day":b.setDate(b.getDate()+n);break;case"hou":b.setHours(b.getHours()+n);break;case"min":b.setMinutes(b.getMinutes()+n);break;case"sec":b.setSeconds(b.getSeconds()+n);break;case"mon":if(i[1]==="month"){b.setMonth(b.getMonth()+n);break}default:var l=e.day[i[1].substring(0,3)];if(typeof l!=="undefined"){var p=l-b.getDay();if(p===0){p=7*n}else{if(p>0){if(i[0]==="last"){p-=7}}else{if(i[0]==="next"){p+=7}}}b.setDate(b.getDate()+p);b.setHours(0,0,0,0)}}break;default:if(/\d+/.test(i[0])){n*=parseInt(i[0],10);switch(i[1].substring(0,3)){case"yea":b.setFullYear(b.getFullYear()+n);break;case"mon":b.setMonth(b.getMonth()+n);break;case"wee":b.setDate(b.getDate()+(n*7));break;case"day":b.setDate(b.getDate()+n);break;case"hou":b.setHours(b.getHours()+n);break;case"min":b.setMinutes(b.getMinutes()+n);break;case"sec":b.setSeconds(b.getSeconds()+n);break}}else{return false}break}return true};g=h.match(/^(\d{2,4}-\d{2}-\d{2})(?:\s(\d{1,2}:\d{2}(:\d{2})?)?(?:\.(\d+))?)?$/);if(g!==null){if(!g[2]){g[2]="00:00:00"}else{if(!g[3]){g[2]+=":00"}}k=g[1].split(/-/g);k[1]=e.mon[k[1]-1]||k[1];k[0]=+k[0];k[0]=(k[0]>=0&&k[0]<=69)?"20"+(k[0]<10?"0"+k[0]:k[0]+""):(k[0]>=70&&k[0]<=99)?"19"+k[0]:k[0]+"";return parseInt(this.strtotime(k[2]+" "+k[1]+" "+k[0]+" "+g[2])+(g[4]?g[4]:""),10)}var j="([+-]?\\d+\\s(years?|months?|weeks?|days?|hours?|min|minutes?|sec|seconds?|sun\\.?|sunday|mon\\.?|monday|tue\\.?|tuesday|wed\\.?|wednesday|thu\\.?|thursday|fri\\.?|friday|sat\\.?|saturday)|(last|next)\\s(years?|months?|weeks?|days?|hours?|min|minutes?|sec|seconds?|sun\\.?|sunday|mon\\.?|monday|tue\\.?|tuesday|wed\\.?|wednesday|thu\\.?|thursday|fri\\.?|friday|sat\\.?|saturday))(\\sago)?";g=h.match(new RegExp(j,"gi"));if(g===null){return false}for(f=0,c=g.length;f<c;f++){if(!a(g[f].split(" "))){return false}}return b.getTime()||0}
+	 
+};
+
+/*
+ * @note uses only non-capturing sub-patterns (unlike PHP original)
+ */
+YamlInline.REGEX_QUOTED_STRING = '(?:"(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\']*(?:\'\'[^\']*)*)\')';
+
+
+/**
+ * YamlParser parses YAML strings to convert them to JS objects
+ * (port of Yaml Symfony Component)
+ */
+var YamlParser = function(offset /* Integer */)
+{
+		this.offset = (offset !== undefined) ? offset : 0;
+};
+YamlParser.prototype =
+{
+	offset: 0,
+	lines: [],
+	currentLineNb: -1,
+	currentLine: '',
+	refs: {},
+	
+	/**
+	 * Parses a YAML string to a JS value.
+	 *
+	 * @param String value A YAML string
+	 *
+	 * @return mixed A JS value
+	 */
+	parse: function(value /* String */)
+	{
+		this.currentLineNb = -1;
+		this.currentLine = '';
+		this.lines = this.cleanup(value).split("\n");
+		
+		var data = null;
+      var context = null;
+      		
+		while ( this.moveToNextLine() )
+		{
+			if ( this.isCurrentLineEmpty() )
+			{
+				continue;
+			}
+			
+			// tab?
+			if ( this.currentLine.charAt(0) == '\t' )
+			{
+				throw new YamlParseException('A YAML file cannot contain tabs as indentation.', this.getRealCurrentLineNb() + 1, this.currentLine);
+			}
+			
+			var isRef = false;
+			var isInPlace = false;
+			var isProcessed = false;
+			var values = null;
+			var matches = null;
+			var c = null;
+			var parser = null;
+			var block = null;
+			var key = null;
+			var parsed = null;
+			var len = null;
+			var reverse = null;
+			
+			if ( values = /^\-((\s+)(.+?))?\s*$/.exec(this.currentLine) )
+			{
+
+				if (context && 'mapping' == context) {
+					throw new YamlParseException('You cannot define a sequence item when in a mapping', this.getRealCurrentLineNb() + 1, this.currentLine);
+				}
+				context = 'sequence';
+
+				if ( !this.isDefined(data) ) data = [];
+				//if ( !(data instanceof Array) ) throw new YamlParseException("Non array entry", this.getRealCurrentLineNb() + 1, this.currentLine);
+				
+				values = {leadspaces: values[2], value: values[3]};
+				
+				if ( this.isDefined(values.value) && ( matches = /^&([^ ]+) *(.*)/.exec(values.value) ) )
+				{
+					matches = {ref: matches[1], value: matches[2]};
+					isRef = matches.ref;
+					values.value = matches.value;
+				}
+				
+				// array
+				if ( !this.isDefined(values.value) || '' == this.trim(values.value) || values.value.replace(/^ +/,'').charAt(0) == '#' )
+				{
+					c = this.getRealCurrentLineNb() + 1;
+					parser = new YamlParser(c);
+					parser.refs = this.refs;
+					data.push(parser.parse(this.getNextEmbedBlock()));
+					this.refs = parser.refs;
+				}
+				else
+				{
+					if ( this.isDefined(values.leadspaces) && 
+						' ' == values.leadspaces && 
+						( matches = new RegExp('^('+YamlInline.REGEX_QUOTED_STRING+'|[^ \'"\{\[].*?) *\:(\\s+(.+?))?\\s*$').exec(values.value) ) 
+					) {
+						matches = {key: matches[1], value: matches[3]};
+						// this is a compact notation element, add to next block and parse
+						c = this.getRealCurrentLineNb();
+						parser = new YamlParser(c);
+						parser.refs = this.refs;
+						block = values.value;
+						
+						if ( !this.isNextLineIndented() )
+						{
+							block += "\n"+this.getNextEmbedBlock(this.getCurrentLineIndentation() + 2);
+						}
+
+						data.push(parser.parse(block));
+						this.refs = parser.refs;
+					}
+					else
+					{
+						data.push(this.parseValue(values.value));
+					}
+				}
+			}
+			else if ( values = new RegExp('^('+YamlInline.REGEX_QUOTED_STRING+'|[^ \'"\[\{].*?) *\:(\\s+(.+?))?\\s*$').exec(this.currentLine) )
+			{
+				if ( !this.isDefined(data) ) data = {};
+				if (context && 'sequence' == context) {
+					throw new YamlParseException('You cannot define a mapping item when in a sequence', this.getRealCurrentLineNb() + 1, this.currentLine);
+				}
+				context = 'mapping';				
+				//if ( data instanceof Array ) throw new YamlParseException("Non mapped entry", this.getRealCurrentLineNb() + 1, this.currentLine);
+				
+				values = {key: values[1], value: values[3]};
+				
+				try {
+					key = new YamlInline().parseScalar(values.key);
+				} catch (e) {
+					if ( e instanceof YamlParseException ) {
+						e.setParsedLine(this.getRealCurrentLineNb() + 1);
+						e.setSnippet(this.currentLine);
+					}
+					throw e;
+				}				
+				
+				
+				if ( '<<' == key )
+				{
+					if ( this.isDefined(values.value) && '*' == (values.value+'').charAt(0) )
+					{
+						isInPlace = values.value.substr(1);
+						if ( this.refs[isInPlace] == undefined )
+						{
+							throw new YamlParseException('Reference "'+value+'" does not exist', this.getRealCurrentLineNb() + 1, this.currentLine);
+						}
+					}
+					else
+					{
+						if ( this.isDefined(values.value) && values.value != '' )
+						{
+							value = values.value;
+						}
+						else
+						{
+							value = this.getNextEmbedBlock();
+						}
+						
+						c = this.getRealCurrentLineNb() + 1;
+						parser = new YamlParser(c);
+						parser.refs = this.refs;
+						parsed = parser.parse(value);
+						this.refs = parser.refs;
+				
+						var merged = [];
+						if ( !this.isObject(parsed) )
+						{
+							throw new YamlParseException("YAML merge keys used with a scalar value instead of an array", this.getRealCurrentLineNb() + 1, this.currentLine);
+						}
+						else if ( this.isDefined(parsed[0]) )
+						{
+							// Numeric array, merge individual elements
+							reverse = this.reverseArray(parsed);
+							len = reverse.length;
+							for ( var i = 0; i < len; i++ )
+							{
+								var parsedItem = reverse[i];
+								if ( !this.isObject(reverse[i]) )
+								{
+									throw new YamlParseException("Merge items must be arrays", this.getRealCurrentLineNb() + 1, this.currentLine);
+								}
+								merged = this.mergeObject(reverse[i], merged);
+							}
+						}
+						else
+						{
+							// Associative array, merge
+							merged = this.mergeObject(merged, parsed);
+						}
+				
+						isProcessed = merged;
+					}
+				}
+				else if ( this.isDefined(values.value) && (matches = /^&([^ ]+) *(.*)/.exec(values.value) ) )
+				{
+					matches = {ref: matches[1], value: matches[2]};
+					isRef = matches.ref;
+					values.value = matches.value;
+				}
+				
+				if ( isProcessed )
+				{
+					// Merge keys
+					data = isProcessed;
+				}
+				// hash
+				else if ( !this.isDefined(values.value) || '' == this.trim(values.value) || this.trim(values.value).charAt(0) == '#' )
+				{
+					// if next line is less indented or equal, then it means that the current value is null
+					if ( this.isNextLineIndented() && !this.isNextLineUnIndentedCollection() )
+					{
+						data[key] = null;
+					}
+					else
+					{
+						c = this.getRealCurrentLineNb() + 1;
+						parser = new YamlParser(c);
+						parser.refs = this.refs;
+						data[key] = parser.parse(this.getNextEmbedBlock());
+						this.refs = parser.refs;
+					}
+				}
+				else
+				{
+					if ( isInPlace )
+					{
+						data = this.refs[isInPlace];
+					}
+					else
+					{
+						data[key] = this.parseValue(values.value);
+					}
+				}
+			}
+			else
+			{
+				// 1-liner followed by newline
+				if ( 2 == this.lines.length && this.isEmpty(this.lines[1]) )
+				{
+					try {
+						value = new YamlInline().parse(this.lines[0]);
+					} catch (e) {
+						if ( e instanceof YamlParseException ) {
+							e.setParsedLine(this.getRealCurrentLineNb() + 1);
+							e.setSnippet(this.currentLine);
+						}
+						throw e;
+					}
+					
+					if ( this.isObject(value) )
+					{
+						var first = value[0];
+						if ( typeof(value) == 'string' && '*' == first.charAt(0) )
+						{
+							data = [];
+							len = value.length;
+							for ( var i = 0; i < len; i++ )
+							{
+								data.push(this.refs[value[i].substr(1)]);
+							}
+							value = data;
+						}
+					}
+				
+					return value;
+				}
+				
+				throw new YamlParseException('Unable to parse.', this.getRealCurrentLineNb() + 1, this.currentLine);
+			}
+		
+			if ( isRef )
+			{
+				if ( data instanceof Array )
+					this.refs[isRef] = data[data.length-1];
+				else
+				{
+					var lastKey = null;
+					for ( var k in data )
+					{
+						if ( data.hasOwnProperty(k) ) lastKey = k;
+					}
+					this.refs[isRef] = data[k];
+				}
+			}
+		}
+		
+		return this.isEmpty(data) ? null : data;
+	},
+
+	/**
+	 * Returns the current line number (takes the offset into account).
+	 *
+	 * @return integer The current line number
+	 */
+	getRealCurrentLineNb: function()
+	{
+		return this.currentLineNb + this.offset;
+	},
+
+	/**
+	 * Returns the current line indentation.
+	 *
+	 * @return integer The current line indentation
+	 */
+	getCurrentLineIndentation: function()
+	{
+		return this.currentLine.length - this.currentLine.replace(/^ +/g, '').length;
+	},
+
+	/**
+	 * Returns the next embed block of YAML.
+	 *
+	 * @param integer indentation The indent level at which the block is to be read, or null for default
+	 *
+	 * @return string A YAML string
+	 *
+	 * @throws YamlParseException When indentation problem are detected
+	 */
+	getNextEmbedBlock: function(indentation)
+	{
+		this.moveToNextLine();
+		var newIndent = null;
+		var indent = null;
+
+		if ( !this.isDefined(indentation) )
+		{
+			newIndent = this.getCurrentLineIndentation();
+			
+			var unindentedEmbedBlock = this.isStringUnIndentedCollectionItem(this.currentLine);
+
+			if ( !this.isCurrentLineEmpty() && 0 == newIndent && !unindentedEmbedBlock )
+			{
+				throw new YamlParseException('Indentation problem A', this.getRealCurrentLineNb() + 1, this.currentLine);
+			}
+		}
+		else
+		{
+			newIndent = indentation;
+		}
+
+		var data = [this.currentLine.substr(newIndent)];
+
+		var isUnindentedCollection = this.isStringUnIndentedCollectionItem(this.currentLine);
+
+		var continuationIndent = -1;
+		if (isUnindentedCollection === true) {
+			continuationIndent = 1 + /^\-((\s+)(.+?))?\s*$/.exec(this.currentLine)[2].length;
+		}
+
+		while ( this.moveToNextLine() )
+		{
+
+			if (isUnindentedCollection && !this.isStringUnIndentedCollectionItem(this.currentLine) && this.getCurrentLineIndentation() != continuationIndent) {
+				this.moveToPreviousLine();
+				break;
+			}
+
+			if ( this.isCurrentLineEmpty() )
+			{
+				if ( this.isCurrentLineBlank() )
+				{
+					data.push(this.currentLine.substr(newIndent));
+				}
+
+				continue;
+			}
+
+			indent = this.getCurrentLineIndentation();
+			var matches;
+			if ( matches = /^( *)$/.exec(this.currentLine) )
+			{
+				// empty line
+				data.push(matches[1]);
+			}
+			else if ( indent >= newIndent )
+			{
+				data.push(this.currentLine.substr(newIndent));
+			}
+			else if ( 0 == indent )
+			{
+				this.moveToPreviousLine();
+
+				break;
+			}
+			else
+			{
+				throw new YamlParseException('Indentation problem B', this.getRealCurrentLineNb() + 1, this.currentLine);
+			}
+		}
+
+		return data.join("\n");
+	},
+
+	/**
+	 * Moves the parser to the next line.
+	 *
+	 * @return Boolean
+	 */
+	moveToNextLine: function()
+	{
+		if ( this.currentLineNb >= this.lines.length - 1 )
+		{
+			return false;
+		}
+
+		this.currentLineNb++;
+		this.currentLine = this.lines[this.currentLineNb];
+
+		return true;
+	},
+
+	/**
+	 * Moves the parser to the previous line.
+	 */
+	moveToPreviousLine: function()
+	{
+		this.currentLineNb--;
+		this.currentLine = this.lines[this.currentLineNb];
+	},
+
+	/**
+	 * Parses a YAML value.
+	 *
+	 * @param string value A YAML value
+	 *
+	 * @return mixed A JS value
+	 *
+	 * @throws YamlParseException When reference does not exist
+	 */
+	parseValue: function(value)
+	{
+		if ( '*' == (value+'').charAt(0) )
+		{
+			if ( this.trim(value).charAt(0) == '#' )
+			{
+				value = (value+'').substr(1, value.indexOf('#') - 2);
+			}
+			else
+			{
+				value = (value+'').substr(1);
+			}
+
+			if ( this.refs[value] == undefined )
+			{
+				throw new YamlParseException('Reference "'+value+'" does not exist', this.getRealCurrentLineNb() + 1, this.currentLine);
+			}
+			return this.refs[value];
+		}
+
+		var matches = null;
+		if ( matches = /^(\||>)(\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?( +#.*)?$/.exec(value) )
+		{
+			matches = {separator: matches[1], modifiers: matches[2], comments: matches[3]};
+			var modifiers = this.isDefined(matches.modifiers) ? matches.modifiers : '';
+
+			return this.parseFoldedScalar(matches.separator, modifiers.replace(/\d+/g, ''), Math.abs(parseInt(modifiers)));
+		}
+		try {
+			return new YamlInline().parse(value);
+		} catch (e) {
+			if ( e instanceof YamlParseException ) {
+				e.setParsedLine(this.getRealCurrentLineNb() + 1);
+				e.setSnippet(this.currentLine);
+			}
+			throw e;
+		}
+	},
+
+	/**
+	 * Parses a folded scalar.
+	 *
+	 * @param	string	separator	 The separator that was used to begin this folded scalar (| or >)
+	 * @param	string	indicator	 The indicator that was used to begin this folded scalar (+ or -)
+	 * @param	integer indentation  The indentation that was used to begin this folded scalar
+	 *
+	 * @return string	The text value
+	 */
+	parseFoldedScalar: function(separator, indicator, indentation)
+	{
+		if ( indicator == undefined ) indicator = '';
+		if ( indentation == undefined ) indentation = 0;
+		
+		separator = '|' == separator ? "\n" : ' ';
+		var text = '';
+		var diff = null;
+
+		var notEOF = this.moveToNextLine();
+
+		while ( notEOF && this.isCurrentLineBlank() )
+		{
+			text += "\n";
+
+			notEOF = this.moveToNextLine();
+		}
+
+		if ( !notEOF )
+		{
+			return '';
+		}
+
+		var matches = null;
+		if ( !(matches = new RegExp('^('+(indentation ? this.strRepeat(' ', indentation) : ' +')+')(.*)$').exec(this.currentLine)) )
+		{
+			this.moveToPreviousLine();
+
+			return '';
+		}
+		
+		matches = {indent: matches[1], text: matches[2]};
+		
+		var textIndent = matches.indent;
+		var previousIndent = 0;
+
+		text += matches.text + separator;
+		while ( this.currentLineNb + 1 < this.lines.length )
+		{
+			this.moveToNextLine();
+			
+			if ( matches = new RegExp('^( {'+textIndent.length+',})(.+)$').exec(this.currentLine) )
+			{
+				matches = {indent: matches[1], text: matches[2]};
+				
+				if ( ' ' == separator && previousIndent != matches.indent )
+				{
+					text = text.substr(0, text.length - 1)+"\n";
+				}
+				
+				previousIndent = matches.indent;
+
+				diff = matches.indent.length - textIndent.length;
+				text += this.strRepeat(' ', diff) + matches.text + (diff != 0 ? "\n" : separator);
+			}
+			else if ( matches = /^( *)$/.exec(this.currentLine) )
+			{
+				text += matches[1].replace(new RegExp('^ {1,'+textIndent.length+'}','g'), '')+"\n";
+			}
+			else
+			{
+				this.moveToPreviousLine();
+
+				break;
+			}
+		}
+
+		if ( ' ' == separator )
+		{
+			// replace last separator by a newline
+			text = text.replace(/ (\n*)$/g, "\n$1");
+		}
+
+		switch ( indicator )
+		{
+			case '':
+				text = text.replace(/\n+$/g, "\n");
+				break;
+			case '+':
+				break;
+			case '-':
+				text = text.replace(/\n+$/g, '');
+				break;
+		}
+
+		return text;
+	},
+
+	/**
+	 * Returns true if the next line is indented.
+	 *
+	 * @return Boolean Returns true if the next line is indented, false otherwise
+	 */
+	isNextLineIndented: function()
+	{
+		var currentIndentation = this.getCurrentLineIndentation();
+		var notEOF = this.moveToNextLine();
+
+		while ( notEOF && this.isCurrentLineEmpty() )
+		{
+			notEOF = this.moveToNextLine();
+		}
+
+		if ( false == notEOF )
+		{
+			return false;
+		}
+
+		var ret = false;
+		if ( this.getCurrentLineIndentation() <= currentIndentation )
+		{
+			ret = true;
+		}
+
+		this.moveToPreviousLine();
+
+		return ret;
+	},
+
+	/**
+	 * Returns true if the current line is blank or if it is a comment line.
+	 *
+	 * @return Boolean Returns true if the current line is empty or if it is a comment line, false otherwise
+	 */
+	isCurrentLineEmpty: function()
+	{
+		return this.isCurrentLineBlank() || this.isCurrentLineComment();
+	},
+
+	/**
+	 * Returns true if the current line is blank.
+	 *
+	 * @return Boolean Returns true if the current line is blank, false otherwise
+	 */
+	isCurrentLineBlank: function()
+	{
+		return '' == this.trim(this.currentLine);
+	},
+
+	/**
+	 * Returns true if the current line is a comment line.
+	 *
+	 * @return Boolean Returns true if the current line is a comment line, false otherwise
+	 */
+	isCurrentLineComment: function()
+	{
+		//checking explicitly the first char of the trim is faster than loops or strpos
+		var ltrimmedLine = this.currentLine.replace(/^ +/g, '');
+		return ltrimmedLine.charAt(0) == '#';
+	},
+
+	/**
+	 * Cleanups a YAML string to be parsed.
+	 *
+	 * @param string value The input YAML string
+	 *
+	 * @return string A cleaned up YAML string
+	 */
+	cleanup: function(value)
+	{
+		value = value.split("\r\n").join("\n").split("\r").join("\n");
+
+		if ( !/\n$/.test(value) )
+		{
+			value += "\n";
+		}
+
+		// strip YAML header
+		var count = 0;
+		var regex = /^\%YAML[: ][\d\.]+.*\n/;
+		while ( regex.test(value) )
+		{
+			value = value.replace(regex, '');
+			count++;
+		}
+		this.offset += count;
+
+		// remove leading comments
+		regex = /^(#.*?\n)+/;
+		if ( regex.test(value) )
+		{
+			var trimmedValue = value.replace(regex, '');
+			
+			// items have been removed, update the offset
+			this.offset += this.subStrCount(value, "\n") - this.subStrCount(trimmedValue, "\n");
+			value = trimmedValue;
+		}
+
+		// remove start of the document marker (---)
+		regex = /^\-\-\-.*?\n/;
+		if ( regex.test(value) )
+		{
+			trimmedValue = value.replace(regex, '');
+			
+			// items have been removed, update the offset
+			this.offset += this.subStrCount(value, "\n") - this.subStrCount(trimmedValue, "\n");
+			value = trimmedValue;
+
+			// remove end of the document marker (...)
+			value = value.replace(/\.\.\.\s*$/g, '');
+		}
+
+		return value;
+	},
+
+	/**
+	 * Returns true if the next line starts unindented collection
+	 *
+	 * @return Boolean Returns true if the next line starts unindented collection, false otherwise
+	 */
+	isNextLineUnIndentedCollection: function()
+	{
+		var currentIndentation = this.getCurrentLineIndentation();
+		var notEOF = this.moveToNextLine();
+
+		while (notEOF && this.isCurrentLineEmpty()) {
+			notEOF = this.moveToNextLine();
+		}
+
+		if (false === notEOF) {
+			return false;
+		}
+
+		var ret = false;
+		if (
+			this.getCurrentLineIndentation() == currentIndentation
+			&&
+			this.isStringUnIndentedCollectionItem(this.currentLine)
+		) {
+			ret = true;
+		}
+
+		this.moveToPreviousLine();
+
+		return ret;
+	},
+
+	/**
+	 * Returns true if the string is unindented collection item
+	 *
+	 * @return Boolean Returns true if the string is unindented collection item, false otherwise
+	 */
+	isStringUnIndentedCollectionItem: function(string)
+	{
+		return (0 === this.currentLine.indexOf('- '));
+	},
+	
+	isObject: function(input)
+	{
+		return typeof(input) == 'object' && this.isDefined(input);
+	},
+	
+	isEmpty: function(input)
+	{
+		return input == undefined || input == null || input == '' || input == 0 || input == "0" || input == false;
+	},
+	
+	isDefined: function(input)
+	{
+		return input != undefined && input != null;
+	},
+	
+	reverseArray: function(input /* Array */)
+	{
+		var result = [];
+		var len = input.length;
+		for ( var i = len-1; i >= 0; i-- )
+		{
+			result.push(input[i]);
+		}
+		
+		return result;
+	},
+	
+	merge: function(a /* Object */, b /* Object */)
+	{
+		var c = {};
+		var i;
+		
+		for ( i in a )
+		{
+			if ( a.hasOwnProperty(i) )
+				if ( /^\d+$/.test(i) ) c.push(a);
+				else c[i] = a[i];
+		}
+		for ( i in b )
+		{
+			if ( b.hasOwnProperty(i) )
+				if ( /^\d+$/.test(i) ) c.push(b);
+				else c[i] = b[i];
+		}
+		
+		return c;
+	},
+	
+	strRepeat: function(str /* String */, count /* Integer */)
+	{
+		var i;
+		var result = '';
+		for ( i = 0; i < count; i++ ) result += str;
+		return result;
+	},
+	
+	subStrCount: function(string, subString, start, length)
+	{
+		var c = 0;
+		
+		string = '' + string;
+		subString = '' + subString;
+		
+		if ( start != undefined ) string = string.substr(start);
+		if ( length != undefined ) string = string.substr(0, length); 
+		
+		var len = string.length;
+		var sublen = subString.length;
+		for ( var i = 0; i < len; i++ )
+		{
+			if ( subString == string.substr(i, sublen) )
+				c++;
+				i += sublen - 1;
+		}
+		
+		return c;
+	},
+	
+	trim: function(str /* String */)
+	{
+		return (str+'').replace(/^ +/,'').replace(/ +$/,'');
+	}
+};
+/**
+ * YamlEscaper encapsulates escaping rules for single and double-quoted
+ * YAML strings.
+ *
+ * @author Matthew Lewinski <matthew@lewinski.org>
+ */
+YamlEscaper = function(){};
+YamlEscaper.prototype =
+{
+	/**
+	 * Determines if a JS value would require double quoting in YAML.
+	 *
+	 * @param string value A JS value
+	 *
+	 * @return Boolean True if the value would require double quotes.
+	 */
+	requiresDoubleQuoting: function(value)
+	{
+		return new RegExp(YamlEscaper.REGEX_CHARACTER_TO_ESCAPE).test(value);
+	},
+
+	/**
+	 * Escapes and surrounds a JS value with double quotes.
+	 *
+	 * @param string value A JS value
+	 *
+	 * @return string The quoted, escaped string
+	 */
+	escapeWithDoubleQuotes: function(value)
+	{
+		value = value + '';
+		var len = YamlEscaper.escapees.length;
+		var maxlen = YamlEscaper.escaped.length;
+		var esc = YamlEscaper.escaped;
+		for (var i = 0; i < len; ++i)
+			if ( i >= maxlen ) esc.push('');
+
+		var ret = '';		
+		ret = value.replace(new RegExp(YamlEscaper.escapees.join('|'),'g'), function(str){
+			for(var i = 0; i < len; ++i){
+				if( str == YamlEscaper.escapees[i] )
+					return esc[i];
+			}
+		});
+		return '"' + ret + '"'; 
+	},
+
+	/**
+	 * Determines if a JS value would require single quoting in YAML.
+	 *
+	 * @param string value A JS value
+	 *
+	 * @return Boolean True if the value would require single quotes.
+	 */
+	requiresSingleQuoting: function(value)
+	{
+		return /[\s'":{}[\],&*#?]|^[-?|<>=!%@`]/.test(value);
+	},
+
+	/**
+	 * Escapes and surrounds a JS value with single quotes.
+	 *
+	 * @param string value A JS value
+	 *
+	 * @return string The quoted, escaped string
+	 */
+	escapeWithSingleQuotes : function(value)
+	{
+		return "'" + value.replace(/'/g, "''") + "'";
+	}
+};
+
+// Characters that would cause a dumped string to require double quoting.
+YamlEscaper.REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9";
+
+// Mapping arrays for escaping a double quoted string. The backslash is
+// first to ensure proper escaping. 
+YamlEscaper.escapees = ['\\\\', '\\"', '"',
+									 "\x00",  "\x01",  "\x02",  "\x03",  "\x04",  "\x05",  "\x06",  "\x07",
+									 "\x08",  "\x09",  "\x0a",  "\x0b",  "\x0c",  "\x0d",  "\x0e",  "\x0f",
+									 "\x10",  "\x11",  "\x12",  "\x13",  "\x14",  "\x15",  "\x16",  "\x17",
+									 "\x18",  "\x19",  "\x1a",  "\x1b",  "\x1c",  "\x1d",  "\x1e",  "\x1f",
+									 "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9"];
+YamlEscaper.escaped = ['\\"', '\\\\', '\\"',
+									 "\\0",   "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\a",
+									 "\\b",   "\\t",   "\\n",   "\\v",   "\\f",   "\\r",   "\\x0e", "\\x0f",
+									 "\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17",
+									 "\\x18", "\\x19", "\\x1a", "\\e",   "\\x1c", "\\x1d", "\\x1e", "\\x1f",
+									 "\\N", "\\_", "\\L", "\\P"];
+/**
+ * YamlUnescaper encapsulates unescaping rules for single and double-quoted
+ * YAML strings.
+ *
+ * @author Matthew Lewinski <matthew@lewinski.org>
+ */
+var YamlUnescaper = function(){};
+YamlUnescaper.prototype =
+{
+	/**
+	 * Unescapes a single quoted string.
+	 *
+	 * @param string value A single quoted string.
+	 *
+	 * @return string The unescaped string.
+	 */
+	unescapeSingleQuotedString: function(value)
+	{
+		return value.replace(/''/g, "'");
+	},
+
+	/**
+	 * Unescapes a double quoted string.
+	 *
+	 * @param string value A double quoted string.
+	 *
+	 * @return string The unescaped string.
+	 */
+	unescapeDoubleQuotedString: function(value)
+	{
+		var callback = function(m) {
+			return new YamlUnescaper().unescapeCharacter(m);
+		};
+
+		// evaluate the string
+		return value.replace(new RegExp(YamlUnescaper.REGEX_ESCAPED_CHARACTER, 'g'), callback);
+	},
+
+	/**
+	 * Unescapes a character that was found in a double-quoted string
+	 *
+	 * @param string value An escaped character
+	 *
+	 * @return string The unescaped character
+	 */
+	unescapeCharacter: function(value)
+	{
+		switch (value.charAt(1)) {
+			case '0':
+				return String.fromCharCode(0);
+			case 'a':
+				return String.fromCharCode(7);
+			case 'b':
+				return String.fromCharCode(8);
+			case 't':
+				return "\t";
+			case "\t":
+				return "\t";
+			case 'n':
+				return "\n";
+			case 'v':
+				return String.fromCharCode(11);
+			case 'f':
+				return String.fromCharCode(12);
+			case 'r':
+				return String.fromCharCode(13);
+			case 'e':
+				return "\x1b";
+			case ' ':
+				return ' ';
+			case '"':
+				return '"';
+			case '/':
+				return '/';
+			case '\\':
+				return '\\';
+			case 'N':
+				// U+0085 NEXT LINE
+				return "\x00\x85";
+			case '_':
+				// U+00A0 NO-BREAK SPACE
+				return "\x00\xA0";
+			case 'L':
+				// U+2028 LINE SEPARATOR
+				return "\x20\x28";
+			case 'P':
+				// U+2029 PARAGRAPH SEPARATOR
+				return "\x20\x29";
+			case 'x':
+				return this.pack('n', new YamlInline().hexdec(value.substr(2, 2)));
+			case 'u':
+				return this.pack('n', new YamlInline().hexdec(value.substr(2, 4)));
+			case 'U':
+				return this.pack('N', new YamlInline().hexdec(value.substr(2, 8)));
+		}
+	},
+	
+	/**
+	 * @see http://phpjs.org/functions/pack
+	 * @warning only modes used above copied
+	 */
+	 pack: function(B){var g=0,o=1,m="",l="",z=0,p=[],E,s,C,I,h,c;var d,b,x,H,u,e,A,q,D,t,w,a,G,F,y,v,f;while(g<B.length){E=B.charAt(g);s="";g++;while((g<B.length)&&(B.charAt(g).match(/[\d\*]/)!==null)){s+=B.charAt(g);g++}if(s===""){s="1"}switch(E){case"n":if(s==="*"){s=arguments.length-o}if(s>(arguments.length-o)){throw new Error("Warning:  pack() Type "+E+": too few arguments")}for(z=0;z<s;z++){m+=String.fromCharCode(arguments[o]>>8&255);m+=String.fromCharCode(arguments[o]&255);o++}break;case"N":if(s==="*"){s=arguments.length-o}if(s>(arguments.length-o)){throw new Error("Warning:  pack() Type "+E+": too few arguments")}for(z=0;z<s;z++){m+=String.fromCharCode(arguments[o]>>24&255);m+=String.fromCharCode(arguments[o]>>16&255);m+=String.fromCharCode(arguments[o]>>8&255);m+=String.fromCharCode(arguments[o]&255);o++}break;default:throw new Error("Warning:  pack() Type "+E+": unknown format code")}}if(o<arguments.length){throw new Error("Warning: pack(): "+(arguments.length-o)+" arguments unused")}return m}
+}
+
+// Regex fragment that matches an escaped character in a double quoted
+// string.
+// why escape quotes, ffs!
+YamlUnescaper.REGEX_ESCAPED_CHARACTER = '\\\\([0abt\tnvfre "\\/\\\\N_LP]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})';
+
+/**
+ * YamlDumper dumps JS variables to YAML strings.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+var YamlDumper = function(){};
+YamlDumper.prototype =
+{
+	/**
+	 * Dumps a JS value to YAML.
+	 *
+	 * @param	mixed	 input	The JS value
+	 * @param	integer inline The level where you switch to inline YAML
+	 * @param	integer indent The level o indentation indentation (used internally)
+	 *
+	 * @return string	The YAML representation of the JS value
+	 */
+	dump: function(input, inline, indent)
+	{
+		if ( inline == null ) inline = 0;
+		if ( indent == null ) indent = 0;
+		var output = '';
+		var prefix = indent ? this.strRepeat(' ', indent) : '';
+		var yaml;
+		if (!this.numSpacesForIndentation) this.numSpacesForIndentation = 2;
+
+		if ( inline <= 0 || !this.isObject(input) || this.isEmpty(input) )
+		{
+			yaml = new YamlInline();
+			output += prefix + yaml.dump(input);
+		}
+		else
+		{
+			var isAHash = !this.arrayEquals(this.getKeys(input), this.range(0,input.length - 1));
+			var willBeInlined;
+			
+			for ( var key in input )
+			{
+				if ( input.hasOwnProperty(key) )
+				{
+					willBeInlined = inline - 1 <= 0 || !this.isObject(input[key]) || this.isEmpty(input[key]);
+					
+					if ( isAHash ) yaml = new YamlInline();
+					
+					output += 
+						prefix + '' +
+						(isAHash ? yaml.dump(key)+':' : '-') + '' +
+						(willBeInlined ? ' ' : "\n") + '' +
+						this.dump(input[key], inline - 1, (willBeInlined ? 0 : indent + this.numSpacesForIndentation)) + '' +
+						(willBeInlined ? "\n" : '');
+				}
+			}
+		}
+
+		return output;
+	},
+	
+	strRepeat: function(str /* String */, count /* Integer */)
+	{
+		var i;
+		var result = '';
+		for ( i = 0; i < count; i++ ) result += str;
+		return result;
+	},
+	
+	isObject: function(input)
+	{
+		return this.isDefined(input) && typeof(input) == 'object';
+	},
+	
+	isEmpty: function(input)
+	{
+		var ret = input == undefined || input == null || input == '' || input == 0 || input == "0" || input == false;
+		if ( !ret && typeof(input) == "object" && !(input instanceof Array)){
+			var propCount = 0;
+			for ( var key in input )
+				if ( input.hasOwnProperty(key) ) propCount++;
+			ret = !propCount;
+		}
+		return ret;
+	},
+	
+	isDefined: function(input)
+	{
+		return input != undefined && input != null;
+	},
+	
+	getKeys: function(tab)
+	{
+		var ret = [];
+		
+		for ( var name in tab )
+		{
+			if ( tab.hasOwnProperty(name) )
+			{
+				ret.push(name);
+			}
+		}
+		
+		return ret;
+	},
+	
+	range: function(start, end)
+	{
+		if ( start > end ) return [];
+		
+		var ret = [];
+		
+		for ( var i = start; i <= end; i++ )
+		{
+			ret.push(i);
+		}
+		
+		return ret;
+	},
+	
+	arrayEquals: function(a,b)
+	{
+		if ( a.length != b.length ) return false;
+		
+		var len = a.length;
+		
+		for ( var i = 0; i < len; i++ )
+		{
+			if ( a[i] != b[i] ) return false;
+		}
+		
+		return true;
+	}
+};
+})();
diff --git a/src/main/webapp/scripts/lib/yamljs/dist/yaml.min.js b/src/main/webapp/scripts/lib/yamljs/dist/yaml.min.js
new file mode 100755
index 0000000000000000000000000000000000000000..fe30b13842b4ffb328d891dd368d3fb2ebc0cb44
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/dist/yaml.min.js
@@ -0,0 +1 @@
+(function e(e,t,n){function i(r,o){if(!t[r]){if(!e[r]){var u=typeof require=="function"&&require;if(!o&&u)return u(r,!0);if(s)return s(r,!0);var a=new Error("Cannot find module '"+r+"'");throw a.code="MODULE_NOT_FOUND",a}var l=t[r]={exports:{}};e[r][0].call(l.exports,function(t){var n=e[r][1][t];return i(n?n:t)},l,l.exports,e,e,t,n)}return t[r].exports}var s=typeof require=="function"&&require;for(var r=0;r<n.length;r++)i(n[r]);return i})({1:[function(n,r,s){var i,t,e;e=n("./Utils");t=n("./Inline");i=function(){function n(){}n.indentation=4;n.prototype.dump=function(n,s,a,l,u){var f,h,p,o,c,r,i;if(s==null){s=0}if(a==null){a=0}if(l==null){l=false}if(u==null){u=null}o="";c=a?e.strRepeat(" ",a):"";if(s<=0||typeof n!=="object"||n instanceof Date||e.isEmpty(n)){o+=c+t.dump(n,l,u)}else{if(n instanceof Array){for(f=0,p=n.length;f<p;f++){r=n[f];i=s-1<=0||typeof r!=="object"||e.isEmpty(r);o+=c+"-"+(i?" ":"\n")+this.dump(r,s-1,i?0:a+this.indentation,l,u)+(i?"\n":"")}}else{for(h in n){r=n[h];i=s-1<=0||typeof r!=="object"||e.isEmpty(r);o+=c+t.dump(h,l,u)+":"+(i?" ":"\n")+this.dump(r,s-1,i?0:a+this.indentation,l,u)+(i?"\n":"")}}}return o};return n}();r.exports=i},{"./Inline":5,"./Utils":9}],2:[function(n,i,r){var t,e;e=n("./Pattern");t=function(){var n;function t(){}t.LIST_ESCAPEES=["\\","\\\\",'\\"','"',"\x00","","","","","","","","\b","	","\n","\x0B","\f","\r","","","","","","","","","","","","","","","","","","",(n=String.fromCharCode)(133),n(160),n(8232),n(8233)];t.LIST_ESCAPED=["\\\\",'\\"','\\"','\\"',"\\0","\\x01","\\x02","\\x03","\\x04","\\x05","\\x06","\\a","\\b","\\t","\\n","\\v","\\f","\\r","\\x0e","\\x0f","\\x10","\\x11","\\x12","\\x13","\\x14","\\x15","\\x16","\\x17","\\x18","\\x19","\\x1a","\\e","\\x1c","\\x1d","\\x1e","\\x1f","\\N","\\_","\\L","\\P"];t.MAPPING_ESCAPEES_TO_ESCAPED=function(){var i,e,r,n;r={};for(i=e=0,n=t.LIST_ESCAPEES.length;0<=n?e<n:e>n;i=0<=n?++e:--e){r[t.LIST_ESCAPEES[i]]=t.LIST_ESCAPED[i]}return r}();t.PATTERN_CHARACTERS_TO_ESCAPE=new e("[\\x00-\\x1f]|…| |
|
");t.PATTERN_MAPPING_ESCAPEES=new e(t.LIST_ESCAPEES.join("|").split("\\").join("\\\\"));t.PATTERN_SINGLE_QUOTING=new e("[\\s'\":{}[\\],&*#?]|^[-?|<>=!%@`]");t.requiresDoubleQuoting=function(e){return this.PATTERN_CHARACTERS_TO_ESCAPE.test(e)};t.escapeWithDoubleQuotes=function(t){var e;e=this.PATTERN_MAPPING_ESCAPEES.replace(t,function(e){return function(t){return e.MAPPING_ESCAPEES_TO_ESCAPED[t]}}(this));return'"'+e+'"'};t.requiresSingleQuoting=function(e){return this.PATTERN_SINGLE_QUOTING.test(e)};t.escapeWithSingleQuotes=function(e){return"'"+e.replace(/'/g,"''")+"'"};return t}();i.exports=t},{"./Pattern":7}],3:[function(r,t,s){var e,n=function(e,t){for(var n in t){if(i.call(t,n))e[n]=t[n]}function r(){this.constructor=e}r.prototype=t.prototype;e.prototype=new r;e.__super__=t.prototype;return e},i={}.hasOwnProperty;e=function(t){n(e,t);function e(e,t,n){this.message=e;this.parsedLine=t;this.snippet=n}e.prototype.toString=function(){if(this.parsedLine!=null&&this.snippet!=null){return"<DumpException> "+this.message+" (line "+this.parsedLine+": '"+this.snippet+"')"}else{return"<DumpException> "+this.message}};return e}(Error);t.exports=e},{}],4:[function(r,t,s){var e,n=function(e,t){for(var n in t){if(i.call(t,n))e[n]=t[n]}function r(){this.constructor=e}r.prototype=t.prototype;e.prototype=new r;e.__super__=t.prototype;return e},i={}.hasOwnProperty;e=function(t){n(e,t);function e(e,t,n){this.message=e;this.parsedLine=t;this.snippet=n}e.prototype.toString=function(){if(this.parsedLine!=null&&this.snippet!=null){return"<ParseException> "+this.message+" (line "+this.parsedLine+": '"+this.snippet+"')"}else{return"<ParseException> "+this.message}};return e}(Error);t.exports=e},{}],5:[function(n,a,f){var o,i,u,t,r,s,e,l=[].indexOf||function(t){for(var e=0,n=this.length;e<n;e++){if(e in this&&this[e]===t)return e}return-1};r=n("./Pattern");s=n("./Unescaper");i=n("./Escaper");e=n("./Utils");t=n("./Exception/ParseException");o=n("./Exception/DumpException");u=function(){function n(){}n.REGEX_QUOTED_STRING="(?:\"(?:[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*)\"|'(?:[^']*(?:''[^']*)*)')";n.PATTERN_TRAILING_COMMENTS=new r("^\\s*#.*$");n.PATTERN_QUOTED_SCALAR=new r("^"+n.REGEX_QUOTED_STRING);n.PATTERN_THOUSAND_NUMERIC_SCALAR=new r("^(-|\\+)?[0-9,]+(\\.[0-9]+)?$");n.PATTERN_SCALAR_BY_DELIMITERS={};n.settings={};n.configure=function(e,t){if(e==null){e=null}if(t==null){t=null}this.settings.exceptionOnInvalidType=e;this.settings.objectDecoder=t};n.parse=function(n,r,s){var i,l;if(r==null){r=false}if(s==null){s=null}this.settings.exceptionOnInvalidType=r;this.settings.objectDecoder=s;if(n==null){return""}n=e.trim(n);if(0===n.length){return""}i={exceptionOnInvalidType:r,objectDecoder:s,i:0};switch(n.charAt(0)){case"[":l=this.parseSequence(n,i);++i.i;break;case"{":l=this.parseMapping(n,i);++i.i;break;default:l=this.parseScalar(n,null,['"',"'"],i)}if(this.PATTERN_TRAILING_COMMENTS.replace(n.slice(i.i),"")!==""){throw new t('Unexpected characters near "'+n.slice(i.i)+'".')}return l};n.dump=function(t,u,r){var s,l,n;if(u==null){u=false}if(r==null){r=null}if(t==null){return"null"}n=typeof t;if(n==="object"){if(t instanceof Date){return t.toISOString()}else if(r!=null){l=r(t);if(typeof l==="string"||l!=null){return l}}return this.dumpObject(t)}if(n==="boolean"){return t?"true":"false"}if(e.isDigits(t)){return n==="string"?"'"+t+"'":String(parseInt(t))}if(e.isNumeric(t)){return n==="string"?"'"+t+"'":String(parseFloat(t))}if(n==="number"){return t===Infinity?".Inf":t===-Infinity?"-.Inf":isNaN(t)?".NaN":t}if(i.requiresDoubleQuoting(t)){return i.escapeWithDoubleQuotes(t)}if(i.requiresSingleQuoting(t)){return i.escapeWithSingleQuotes(t)}if(""===t){return'""'}if(e.PATTERN_DATE.test(t)){return"'"+t+"'"}if((s=t.toLowerCase())==="null"||s==="~"||s==="true"||s==="false"){return"'"+t+"'"}return t};n.dumpObject=function(t,u,s){var n,r,l,e,i;if(s==null){s=null}if(t instanceof Array){e=[];for(n=0,l=t.length;n<l;n++){i=t[n];e.push(this.dump(i))}return"["+e.join(", ")+"]"}else{e=[];for(r in t){i=t[r];e.push(this.dump(r)+": "+this.dump(i))}return"{"+e.join(", ")+"}"}};n.parseScalar=function(u,a,c,s,h){var i,f,_,n,o,E,T,p,A;if(a==null){a=null}if(c==null){c=['"',"'"]}if(s==null){s=null}if(h==null){h=true}if(s==null){s={exceptionOnInvalidType:this.settings.exceptionOnInvalidType,objectDecoder:this.settings.objectDecoder,i:0}}i=s.i;if(E=u.charAt(i),l.call(c,E)>=0){n=this.parseQuotedScalar(u,s);i=s.i;if(a!=null){A=e.ltrim(u.slice(i)," ");if(!(T=A.charAt(0),l.call(a,T)>=0)){throw new t("Unexpected characters ("+u.slice(i)+").")}}}else{if(!a){n=u.slice(i);i+=n.length;p=n.indexOf(" #");if(p!==-1){n=e.rtrim(n.slice(0,p))}}else{f=a.join("|");o=this.PATTERN_SCALAR_BY_DELIMITERS[f];if(o==null){o=new r("^(.+?)("+f+")");this.PATTERN_SCALAR_BY_DELIMITERS[f]=o}if(_=o.exec(u.slice(i))){n=_[1];i+=n.length}else{throw new t("Malformed inline YAML string ("+u+").")}}if(h){n=this.evaluateScalar(n,s)}}s.i=i;return n};n.parseQuotedScalar=function(r,l){var e,i,n;e=l.i;if(!(i=this.PATTERN_QUOTED_SCALAR.exec(r.slice(e)))){throw new t("Malformed inline YAML string ("+r.slice(e)+").")}n=i[0].substr(1,i[0].length-2);if('"'===r.charAt(e)){n=s.unescapeDoubleQuotedString(n)}else{n=s.unescapeSingleQuotedString(n)}e+=i[0].length;l.i=e;return n};n.parseSequence=function(i,n){var o,f,e,l,u,s,a,r;s=[];u=i.length;e=n.i;e+=1;while(e<u){n.i=e;switch(i.charAt(e)){case"[":s.push(this.parseSequence(i,n));e=n.i;break;case"{":s.push(this.parseMapping(i,n));e=n.i;break;case"]":return s;case",":case" ":case"\n":break;default:l=(a=i.charAt(e))==='"'||a==="'";r=this.parseScalar(i,[",","]"],['"',"'"],n);e=n.i;if(!l&&typeof r==="string"&&(r.indexOf(": ")!==-1||r.indexOf(":\n")!==-1)){try{r=this.parseMapping("{"+r+"}")}catch(f){o=f}}s.push(r);--e}++e}throw new t("Malformed inline YAML string "+i)};n.parseMapping=function(i,n){var u,e,s,o,r,a,l;r={};o=i.length;e=n.i;e+=1;a=false;while(e<o){n.i=e;switch(i.charAt(e)){case" ":case",":case"\n":++e;n.i=e;a=true;break;case"}":return r}if(a){a=false;continue}s=this.parseScalar(i,[":"," ","\n"],['"',"'"],n,false);e=n.i;u=false;while(e<o){n.i=e;switch(i.charAt(e)){case"[":l=this.parseSequence(i,n);e=n.i;if(r[s]===void 0){r[s]=l}u=true;break;case"{":l=this.parseMapping(i,n);e=n.i;if(r[s]===void 0){r[s]=l}u=true;break;case":":case" ":case"\n":break;default:l=this.parseScalar(i,[",","}"],['"',"'"],n);e=n.i;if(r[s]===void 0){r[s]=l}u=true;--e}++e;if(u){break}}}throw new t("Malformed inline YAML string "+i)};n.evaluateScalar=function(n,a){var s,h,p,E,i,c,o,r,l,f,u;n=e.trim(n);l=n.toLowerCase();switch(l){case"null":case"":case"~":return null;case"true":return true;case"false":return false;case".inf":return Infinity;case".nan":return NaN;case"-.inf":return Infinity;default:E=l.charAt(0);switch(E){case"!":i=n.indexOf(" ");if(i===-1){c=l}else{c=l.slice(0,i)}switch(c){case"!":if(i!==-1){return parseInt(this.parseScalar(n.slice(2)))}return null;case"!str":return e.ltrim(n.slice(4));case"!!str":return e.ltrim(n.slice(5));case"!!int":return parseInt(this.parseScalar(n.slice(5)));case"!!bool":return e.parseBoolean(this.parseScalar(n.slice(6)),false);case"!!float":return parseFloat(this.parseScalar(n.slice(7)));case"!!timestamp":return e.stringToDate(e.ltrim(n.slice(11)));default:if(a==null){a={exceptionOnInvalidType:this.settings.exceptionOnInvalidType,objectDecoder:this.settings.objectDecoder,i:0}}o=a.objectDecoder,p=a.exceptionOnInvalidType;if(o){u=e.rtrim(n);i=u.indexOf(" ");if(i===-1){return o(u,null)}else{f=e.ltrim(u.slice(i+1));if(!(f.length>0)){f=null}return o(u.slice(0,i),f)}}if(p){throw new t("Custom object support when parsing a YAML file has been disabled.")}return null}break;case"0":if("0x"===n.slice(0,2)){return e.hexDec(n)}else if(e.isDigits(n)){return e.octDec(n)}else if(e.isNumeric(n)){return parseFloat(n)}else{return n}break;case"+":if(e.isDigits(n)){r=n;s=parseInt(r);if(r===String(s)){return s}else{return r}}else if(e.isNumeric(n)){return parseFloat(n)}else if(this.PATTERN_THOUSAND_NUMERIC_SCALAR.test(n)){return parseFloat(n.replace(",",""))}return n;case"-":if(e.isDigits(n.slice(1))){if("0"===n.charAt(1)){return-e.octDec(n.slice(1))}else{r=n.slice(1);s=parseInt(r);if(r===String(s)){return-s}else{return-r}}}else if(e.isNumeric(n)){return parseFloat(n)}else if(this.PATTERN_THOUSAND_NUMERIC_SCALAR.test(n)){return parseFloat(n.replace(",",""))}return n;default:if(h=e.stringToDate(n)){return h}else if(e.isNumeric(n)){return parseFloat(n)}else if(this.PATTERN_THOUSAND_NUMERIC_SCALAR.test(n)){return parseFloat(n.replace(",",""))}return n}}};return n}();a.exports=u},{"./Escaper":2,"./Exception/DumpException":3,"./Exception/ParseException":4,"./Pattern":7,"./Unescaper":8,"./Utils":9}],6:[function(r,l,u){var i,n,s,t,e;i=r("./Inline");t=r("./Pattern");e=r("./Utils");n=r("./Exception/ParseException");s=function(){r.prototype.PATTERN_FOLDED_SCALAR_ALL=new t("^(?:(?<type>![^\\|>]*)\\s+)?(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$");r.prototype.PATTERN_FOLDED_SCALAR_END=new t("(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$");r.prototype.PATTERN_SEQUENCE_ITEM=new t("^\\-((?<leadspaces>\\s+)(?<value>.+?))?\\s*$");r.prototype.PATTERN_ANCHOR_VALUE=new t("^&(?<ref>[^ ]+) *(?<value>.*)");r.prototype.PATTERN_COMPACT_NOTATION=new t("^(?<key>"+i.REGEX_QUOTED_STRING+"|[^ '\"\\{\\[].*?) *\\:(\\s+(?<value>.+?))?\\s*$");r.prototype.PATTERN_MAPPING_ITEM=new t("^(?<key>"+i.REGEX_QUOTED_STRING+"|[^ '\"\\[\\{].*?) *\\:(\\s+(?<value>.+?))?\\s*$");r.prototype.PATTERN_DECIMAL=new t("\\d+");r.prototype.PATTERN_INDENT_SPACES=new t("^ +");r.prototype.PATTERN_TRAILING_LINES=new t("(\n*)$");r.prototype.PATTERN_YAML_HEADER=new t("^\\%YAML[: ][\\d\\.]+.*\n");r.prototype.PATTERN_LEADING_COMMENTS=new t("^(\\#.*?\n)+");r.prototype.PATTERN_DOCUMENT_MARKER_START=new t("^\\-\\-\\-.*?\n");r.prototype.PATTERN_DOCUMENT_MARKER_END=new t("^\\.\\.\\.\\s*$");r.prototype.PATTERN_FOLDED_SCALAR_BY_INDENTATION={};r.prototype.CONTEXT_NONE=0;r.prototype.CONTEXT_SEQUENCE=1;r.prototype.CONTEXT_MAPPING=2;function r(e){this.offset=e!=null?e:0;this.lines=[];this.currentLineNb=-1;this.currentLine="";this.refs={}}r.prototype.parse=function(l,f,c){var U,N,v,p,d,t,o,Q,B,Y,g,E,G,L,S,b,u,R,y,j,X,D,O,P,m,_,w,x,M,A,h,a,F,k,H,I,T,C,s;if(f==null){f=false}if(c==null){c=null}this.currentLineNb=-1;this.currentLine="";this.lines=this.cleanup(l).split("\n");t=null;d=this.CONTEXT_NONE;N=false;while(this.moveToNextLine()){if(this.isCurrentLineEmpty()){continue}if("	"===this.currentLine[0]){throw new n("A YAML file cannot contain tabs as indentation.",this.getRealCurrentLineNb()+1,this.currentLine)}L=w=false;if(s=this.PATTERN_SEQUENCE_ITEM.exec(this.currentLine)){if(this.CONTEXT_MAPPING===d){throw new n("You cannot define a sequence item when in a mapping")}d=this.CONTEXT_SEQUENCE;if(t==null){t=[]}if(s.value!=null&&(_=this.PATTERN_ANCHOR_VALUE.exec(s.value))){L=_.ref;s.value=_.value}if(!(s.value!=null)||""===e.trim(s.value," ")||e.ltrim(s.value," ").indexOf("#")===0){if(this.currentLineNb<this.lines.length-1&&!this.isNextLineUnIndentedCollection()){p=this.getRealCurrentLineNb()+1;a=new r(p);a.refs=this.refs;t.push(a.parse(this.getNextEmbedBlock(null,true),f,c))}else{t.push(null)}}else{if(((F=s.leadspaces)!=null?F.length:void 0)&&(_=this.PATTERN_COMPACT_NOTATION.exec(s.value))){p=this.getRealCurrentLineNb();a=new r(p);a.refs=this.refs;v=s.value;G=this.getCurrentLineIndentation();if(this.isNextLineIndented(false)){v+="\n"+this.getNextEmbedBlock(G+s.leadspaces.length+1,true)}t.push(a.parse(v,f,c))}else{t.push(this.parseValue(s.value,f,c))}}}else if((s=this.PATTERN_MAPPING_ITEM.exec(this.currentLine))&&s.key.indexOf(" #")===-1){if(this.CONTEXT_SEQUENCE===d){throw new n("You cannot define a mapping item when in a sequence")}d=this.CONTEXT_MAPPING;if(t==null){t={}}i.configure(f,c);try{u=i.parseScalar(s.key)}catch(Q){o=Q;o.parsedLine=this.getRealCurrentLineNb()+1;o.snippet=this.currentLine;throw o}if("<<"===u){w=true;N=true;if(((k=s.value)!=null?k.indexOf("*"):void 0)===0){I=s.value.slice(1);if(this.refs[I]==null){throw new n('Reference "'+I+'" does not exist.',this.getRealCurrentLineNb()+1,this.currentLine)}T=this.refs[I];if(typeof T!=="object"){throw new n("YAML merge keys used with a scalar value instead of an object.",this.getRealCurrentLineNb()+1,this.currentLine)}if(T instanceof Array){for(E=S=0,j=T.length;S<j;E=++S){l=T[E];if(t[M=String(E)]==null){t[M]=l}}}else{for(u in T){l=T[u];if(t[u]==null){t[u]=l}}}}else{if(s.value!=null&&s.value!==""){l=s.value}else{l=this.getNextEmbedBlock()}p=this.getRealCurrentLineNb()+1;a=new r(p);a.refs=this.refs;A=a.parse(l,f);if(typeof A!=="object"){throw new n("YAML merge keys used with a scalar value instead of an object.",this.getRealCurrentLineNb()+1,this.currentLine)}if(A instanceof Array){for(R=0,X=A.length;R<X;R++){h=A[R];if(typeof h!=="object"){throw new n("Merge items must be objects.",this.getRealCurrentLineNb()+1,h)}if(h instanceof Array){for(E=m=0,D=h.length;m<D;E=++m){l=h[E];b=String(E);if(!t.hasOwnProperty(b)){t[b]=l}}}else{for(u in h){l=h[u];if(!t.hasOwnProperty(u)){t[u]=l}}}}}else{for(u in A){l=A[u];if(!t.hasOwnProperty(u)){t[u]=l}}}}}else if(s.value!=null&&(_=this.PATTERN_ANCHOR_VALUE.exec(s.value))){L=_.ref;s.value=_.value}if(w){}else if(!(s.value!=null)||""===e.trim(s.value," ")||e.ltrim(s.value," ").indexOf("#")===0){if(!this.isNextLineIndented()&&!this.isNextLineUnIndentedCollection()){if(N||t[u]===void 0){t[u]=null}}else{p=this.getRealCurrentLineNb()+1;a=new r(p);a.refs=this.refs;C=a.parse(this.getNextEmbedBlock(),f,c);if(N||t[u]===void 0){t[u]=C}}}else{C=this.parseValue(s.value,f,c);if(N||t[u]===void 0){t[u]=C}}}else{P=this.lines.length;if(1===P||2===P&&e.isEmpty(this.lines[1])){try{l=i.parse(this.lines[0],f,c)}catch(B){o=B;o.parsedLine=this.getRealCurrentLineNb()+1;o.snippet=this.currentLine;throw o}if(typeof l==="object"){if(l instanceof Array){g=l[0]}else{for(u in l){g=l[u];break}}if(typeof g==="string"&&g.indexOf("*")===0){t=[];for(x=0,O=l.length;x<O;x++){U=l[x];t.push(this.refs[U.slice(1)])}l=t}}return l}else if((H=e.ltrim(l).charAt(0))==="["||H==="{"){try{return i.parse(l,f,c)}catch(Y){o=Y;o.parsedLine=this.getRealCurrentLineNb()+1;o.snippet=this.currentLine;throw o}}throw new n("Unable to parse.",this.getRealCurrentLineNb()+1,this.currentLine)}if(L){if(t instanceof Array){this.refs[L]=t[t.length-1]}else{y=null;for(u in t){y=u}this.refs[L]=t[y]}}}if(e.isEmpty(t)){return null}else{return t}};r.prototype.getRealCurrentLineNb=function(){return this.currentLineNb+this.offset};r.prototype.getCurrentLineIndentation=function(){return this.currentLine.length-e.ltrim(this.currentLine," ").length};r.prototype.getNextEmbedBlock=function(r,l){var s,i,o,t,u,a,f;if(r==null){r=null}if(l==null){l=false}this.moveToNextLine();if(r==null){t=this.getCurrentLineIndentation();f=this.isStringUnIndentedCollectionItem(this.currentLine);if(!this.isCurrentLineEmpty()&&0===t&&!f){throw new n("Indentation problem.",this.getRealCurrentLineNb()+1,this.currentLine)}}else{t=r}s=[this.currentLine.slice(t)];if(!l){o=this.isStringUnIndentedCollectionItem(this.currentLine)}a=this.PATTERN_FOLDED_SCALAR_END;u=!a.test(this.currentLine);while(this.moveToNextLine()){i=this.getCurrentLineIndentation();if(i===t){u=!a.test(this.currentLine)}if(o&&!this.isStringUnIndentedCollectionItem(this.currentLine)&&i===t){this.moveToPreviousLine();break}if(this.isCurrentLineBlank()){s.push(this.currentLine.slice(t));continue}if(u&&this.isCurrentLineComment()){if(i===t){continue}}if(i>=t){s.push(this.currentLine.slice(t))}else if(e.ltrim(this.currentLine).charAt(0)==="#"){}else if(0===i){this.moveToPreviousLine();break}else{throw new n("Indentation problem.",this.getRealCurrentLineNb()+1,this.currentLine)}}return s.join("\n")};r.prototype.moveToNextLine=function(){if(this.currentLineNb>=this.lines.length-1){return false}this.currentLine=this.lines[++this.currentLineNb];return true};r.prototype.moveToPreviousLine=function(){this.currentLine=this.lines[--this.currentLineNb]};r.prototype.parseValue=function(e,l,u){var t,p,E,s,r,a,o,c,h,f;if(0===e.indexOf("*")){o=e.indexOf("#");if(o!==-1){e=e.substr(1,o-2)}else{e=e.slice(1)}if(this.refs[e]===void 0){throw new n('Reference "'+e+'" does not exist.',this.currentLine)}return this.refs[e]}if(r=this.PATTERN_FOLDED_SCALAR_ALL.exec(e)){a=(c=r.modifiers)!=null?c:"";s=Math.abs(parseInt(a));if(isNaN(s)){s=0}f=this.parseFoldedScalar(r.separator,this.PATTERN_DECIMAL.replace(a,""),s);if(r.type!=null){i.configure(l,u);return i.parseScalar(r.type+" "+f)}else{return f}}try{return i.parse(e,l,u)}catch(p){t=p;if(((h=e.charAt(0))==="["||h==="{")&&t instanceof n&&this.isNextLineIndented()){e+="\n"+this.getNextEmbedBlock();try{return i.parse(e,l,u)}catch(E){t=E;t.parsedLine=this.getRealCurrentLineNb()+1;t.snippet=this.currentLine;throw t}}else{t.parsedLine=this.getRealCurrentLineNb()+1;t.snippet=this.currentLine;throw t}}};r.prototype.parseFoldedScalar=function(T,o,i){var l,h,E,a,c,f,s,u,p,n;if(o==null){o=""}if(i==null){i=0}s=this.moveToNextLine();if(!s){return""}l=this.isCurrentLineBlank();n="";while(s&&l){if(s=this.moveToNextLine()){n+="\n";l=this.isCurrentLineBlank()}}if(0===i){if(c=this.PATTERN_INDENT_SPACES.exec(this.currentLine)){i=c[0].length}}if(i>0){u=this.PATTERN_FOLDED_SCALAR_BY_INDENTATION[i];if(u==null){u=new t("^ {"+i+"}(.*)$");r.prototype.PATTERN_FOLDED_SCALAR_BY_INDENTATION[i]=u}while(s&&(l||(c=u.exec(this.currentLine)))){if(l){n+=this.currentLine.slice(i)}else{n+=c[1]}if(s=this.moveToNextLine()){n+="\n";l=this.isCurrentLineBlank()}}}else if(s){n+="\n"}if(s){this.moveToPreviousLine()}if(">"===T){f="";p=n.split("\n");for(h=0,E=p.length;h<E;h++){a=p[h];if(a.length===0||a.charAt(0)===" "){f=e.rtrim(f," ")+a+"\n"}else{f+=a+" "}}n=f}if("+"!==o){n=e.rtrim(n)}if(""===o){n=this.PATTERN_TRAILING_LINES.replace(n,"\n")}else if("-"===o){n=this.PATTERN_TRAILING_LINES.replace(n,"")}return n};r.prototype.isNextLineIndented=function(t){var e,i,n;if(t==null){t=true}i=this.getCurrentLineIndentation();e=!this.moveToNextLine();if(t){while(!e&&this.isCurrentLineEmpty()){e=!this.moveToNextLine()}}else{while(!e&&this.isCurrentLineBlank()){e=!this.moveToNextLine()}}if(e){return false}n=false;if(this.getCurrentLineIndentation()>i){n=true}this.moveToPreviousLine();return n};r.prototype.isCurrentLineEmpty=function(){var t;t=e.trim(this.currentLine," ");return t.length===0||t.charAt(0)==="#"};r.prototype.isCurrentLineBlank=function(){return""===e.trim(this.currentLine," ")};r.prototype.isCurrentLineComment=function(){var t;t=e.ltrim(this.currentLine," ");return t.charAt(0)==="#"};r.prototype.cleanup=function(t){var i,a,f,u,o,E,T,s,n,c,h,p,l,r;if(t.indexOf("\r")!==-1){t=t.split("\r\n").join("\n").split("\r").join("\n")}i=0;c=this.PATTERN_YAML_HEADER.replaceAll(t,""),t=c[0],i=c[1];this.offset+=i;h=this.PATTERN_LEADING_COMMENTS.replaceAll(t,"",1),r=h[0],i=h[1];if(i===1){this.offset+=e.subStrCount(t,"\n")-e.subStrCount(r,"\n");t=r}p=this.PATTERN_DOCUMENT_MARKER_START.replaceAll(t,"",1),r=p[0],i=p[1];if(i===1){this.offset+=e.subStrCount(t,"\n")-e.subStrCount(r,"\n");t=r;t=this.PATTERN_DOCUMENT_MARKER_END.replace(t,"")}n=t.split("\n");l=-1;for(u=0,E=n.length;u<E;u++){s=n[u];if(e.trim(s," ").length===0){continue}f=s.length-e.ltrim(s).length;if(l===-1||f<l){l=f}}if(l>0){for(a=o=0,T=n.length;o<T;a=++o){s=n[a];n[a]=s.slice(l)}t=n.join("\n")}return t};r.prototype.isNextLineUnIndentedCollection=function(e){var t,n;if(e==null){e=null}if(e==null){e=this.getCurrentLineIndentation()}t=this.moveToNextLine();while(t&&this.isCurrentLineEmpty()){t=this.moveToNextLine()}if(false===t){return false}n=false;if(this.getCurrentLineIndentation()===e&&this.isStringUnIndentedCollectionItem(this.currentLine)){n=true}this.moveToPreviousLine();return n};r.prototype.isStringUnIndentedCollectionItem=function(){return this.currentLine==="-"||this.currentLine.slice(0,2)==="- "};return r}();l.exports=s},{"./Exception/ParseException":4,"./Inline":5,"./Pattern":7,"./Utils":9}],7:[function(n,t,i){var e;e=function(){e.prototype.regex=null;e.prototype.rawRegex=null;e.prototype.cleanedRegex=null;e.prototype.mapping=null;function e(i,o){var n,s,t,e,l,r,u,a,f;if(o==null){o=""}t="";l=i.length;r=null;s=0;e=0;while(e<l){n=i.charAt(e);if(n==="\\"){t+=i.slice(e,+(e+1)+1||9e9);e++}else if(n==="("){if(e<l-2){a=i.slice(e,+(e+2)+1||9e9);if(a==="(?:"){e+=2;t+=a}else if(a==="(?<"){s++;e+=2;u="";while(e+1<l){f=i.charAt(e+1);if(f===">"){t+="(";e++;if(u.length>0){if(r==null){r={}}r[u]=s}break}else{u+=f}e++}}else{t+=n;s++}}else{t+=n}}else{t+=n}e++}this.rawRegex=i;this.cleanedRegex=t;this.regex=new RegExp(this.cleanedRegex,"g"+o.replace("g",""));this.mapping=r}e.prototype.exec=function(r){var i,e,t,n;this.regex.lastIndex=0;e=this.regex.exec(r);if(e==null){return null}if(this.mapping!=null){n=this.mapping;for(t in n){i=n[t];e[t]=e[i]}}return e};e.prototype.test=function(e){this.regex.lastIndex=0;return this.regex.test(e)};e.prototype.replace=function(e,t){this.regex.lastIndex=0;return e.replace(this.regex,t)};e.prototype.replaceAll=function(e,i,t){var n;if(t==null){t=0}this.regex.lastIndex=0;n=0;while(this.regex.test(e)&&(t===0||n<t)){this.regex.lastIndex=0;e=e.replace(this.regex,"");n++}return[e,n]};return e}();t.exports=e},{}],8:[function(t,r,s){var n,i,e;e=t("./Utils");n=t("./Pattern");i=function(){function t(){}t.PATTERN_ESCAPED_CHARACTER=new n('\\\\([0abt	nvfre "\\/\\\\N_LP]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})');t.unescapeSingleQuotedString=function(e){return e.replace(/\'\'/g,"'")};t.unescapeDoubleQuotedString=function(e){if(this._unescapeCallback==null){this._unescapeCallback=function(e){return function(t){return e.unescapeCharacter(t)}}(this)}return this.PATTERN_ESCAPED_CHARACTER.replace(e,this._unescapeCallback)};t.unescapeCharacter=function(n){var t;t=String.fromCharCode;switch(n.charAt(1)){case"0":return t(0);case"a":return t(7);case"b":return t(8);case"t":return"	";case"	":return"	";case"n":return"\n";case"v":return t(11);case"f":return t(12);case"r":return t(13);case"e":return t(27);case" ":return" ";case'"':return'"';case"/":return"/";case"\\":return"\\";case"N":return t(133);case"_":return t(160);case"L":return t(8232);case"P":return t(8233);case"x":return e.utf8chr(e.hexDec(n.substr(2,2)));case"u":return e.utf8chr(e.hexDec(n.substr(2,4)));case"U":return e.utf8chr(e.hexDec(n.substr(2,8)));default:return""}};return t}();r.exports=i},{"./Pattern":7,"./Utils":9}],9:[function(e,i,r){var t,n;t=e("./Pattern");n=function(){function n(){}n.REGEX_LEFT_TRIM_BY_CHAR={};n.REGEX_RIGHT_TRIM_BY_CHAR={};n.REGEX_SPACES=/\s+/g;n.REGEX_DIGITS=/^\d+$/;n.REGEX_OCTAL=/[^0-7]/gi;n.REGEX_HEXADECIMAL=/[^a-f0-9]/gi;n.PATTERN_DATE=new t("^"+"(?<year>[0-9][0-9][0-9][0-9])"+"-(?<month>[0-9][0-9]?)"+"-(?<day>[0-9][0-9]?)"+"(?:(?:[Tt]|[ 	]+)"+"(?<hour>[0-9][0-9]?)"+":(?<minute>[0-9][0-9])"+":(?<second>[0-9][0-9])"+"(?:.(?<fraction>[0-9]*))?"+"(?:[ 	]*(?<tz>Z|(?<tz_sign>[-+])(?<tz_hour>[0-9][0-9]?)"+"(?::(?<tz_minute>[0-9][0-9]))?))?)?"+"$","i");n.LOCAL_TIMEZONE_OFFSET=(new Date).getTimezoneOffset()*60*1e3;n.trim=function(i,e){var t,n;if(e==null){e="\\s"}return i.trim();t=this.REGEX_LEFT_TRIM_BY_CHAR[e];if(t==null){this.REGEX_LEFT_TRIM_BY_CHAR[e]=t=new RegExp("^"+e+""+e+"*")}t.lastIndex=0;n=this.REGEX_RIGHT_TRIM_BY_CHAR[e];if(n==null){this.REGEX_RIGHT_TRIM_BY_CHAR[e]=n=new RegExp(e+""+e+"*$")}n.lastIndex=0;return i.replace(t,"").replace(n,"")};n.ltrim=function(n,e){var t;if(e==null){e="\\s"}t=this.REGEX_LEFT_TRIM_BY_CHAR[e];if(t==null){this.REGEX_LEFT_TRIM_BY_CHAR[e]=t=new RegExp("^"+e+""+e+"*")}t.lastIndex=0;return n.replace(t,"")};n.rtrim=function(n,e){var t;if(e==null){e="\\s"}t=this.REGEX_RIGHT_TRIM_BY_CHAR[e];if(t==null){this.REGEX_RIGHT_TRIM_BY_CHAR[e]=t=new RegExp(e+""+e+"*$")}t.lastIndex=0;return n.replace(t,"")};n.isEmpty=function(e){return!e||e===""||e==="0"||e instanceof Array&&e.length===0};n.subStrCount=function(e,i,u,a){var s,r,t,o,n,l;s=0;e=""+e;i=""+i;if(u!=null){e=e.slice(u)}if(a!=null){e=e.slice(0,a)}o=e.length;l=i.length;for(r=t=0,n=o;0<=n?t<n:t>n;r=0<=n?++t:--t){if(i===e.slice(r,l)){s++;r+=l-1}}return s};n.isDigits=function(e){this.REGEX_DIGITS.lastIndex=0;return this.REGEX_DIGITS.test(e)};n.octDec=function(e){this.REGEX_OCTAL.lastIndex=0;return parseInt((e+"").replace(this.REGEX_OCTAL,""),8)};n.hexDec=function(e){this.REGEX_HEXADECIMAL.lastIndex=0;e=this.trim(e);if((e+"").slice(0,2)==="0x"){e=(e+"").slice(2)}return parseInt((e+"").replace(this.REGEX_HEXADECIMAL,""),16)};n.utf8chr=function(e){var t;t=String.fromCharCode;if(128>(e%=2097152)){return t(e)}if(2048>e){return t(192|e>>6)+t(128|e&63)}if(65536>e){return t(224|e>>12)+t(128|e>>6&63)+t(128|e&63)}return t(240|e>>18)+t(128|e>>12&63)+t(128|e>>6&63)+t(128|e&63)};n.parseBoolean=function(t,n){var e;if(n==null){n=true}if(typeof t==="string"){e=t.toLowerCase();if(!n){if(e==="no"){return false}}if(e==="0"){return false}if(e==="false"){return false}if(e===""){return false}return true}return!!t};n.isNumeric=function(e){this.REGEX_SPACES.lastIndex=0;return typeof e==="number"||typeof e==="string"&&!isNaN(e)&&e.replace(this.REGEX_SPACES,"")!==""};n.stringToDate=function(r){var n,l,t,c,e,h,u,f,o,a,i,s;if(!(r!=null?r.length:void 0)){return null}e=this.PATTERN_DATE.exec(r);if(!e){return null}s=parseInt(e.year,10);u=parseInt(e.month,10)-1;l=parseInt(e.day,10);if(e.hour==null){n=new Date(Date.UTC(s,u,l));return n}c=parseInt(e.hour,10);h=parseInt(e.minute,10);f=parseInt(e.second,10);if(e.fraction!=null){t=e.fraction.slice(0,3);while(t.length<3){t+="0"}t=parseInt(t,10)}else{t=0}if(e.tz!=null){o=parseInt(e.tz_hour,10);if(e.tz_minute!=null){a=parseInt(e.tz_minute,10)}else{a=0}i=(o*60+a)*6e4;if("-"===e.tz_sign){i*=-1}}n=new Date(Date.UTC(s,u,l,c,h,f,t));if(i){n.setTime(n.getTime()+i)}return n};n.strRepeat=function(n,i){var e,t;t="";e=0;while(e<i){t+=n;e++}return t};n.getStringFromFile=function(i,n){var s,l,r,a,f,u,o,t;if(n==null){n=null}t=null;if(typeof window!=="undefined"&&window!==null){if(window.XMLHttpRequest){t=new XMLHttpRequest}else if(window.ActiveXObject){u=["Msxml2.XMLHTTP.6.0","Msxml2.XMLHTTP.3.0","Msxml2.XMLHTTP","Microsoft.XMLHTTP"];for(r=0,a=u.length;r<a;r++){f=u[r];try{t=new ActiveXObject(f)}catch(c){}}}}if(t!=null){if(n!=null){t.onreadystatechange=function(){if(t.readyState===4){if(t.status===200||t.status===0){return n(t.responseText)}else{return n(null)}}};t.open("GET",i,true);return t.send(null)}else{t.open("GET",i,false);t.send(null);if(t.status===200||t.status===0){return t.responseText}return null}}else{o=e;l=o("fs");if(n!=null){return l.readFile(i,function(e,t){if(e){return n(null)}else{return n(String(t))}})}else{s=l.readFileSync(i);if(s!=null){return String(s)}return null}}};return n}();i.exports=n},{"./Pattern":7}],10:[function(e,s,l){var i,r,n,t;r=e("./Parser");i=e("./Dumper");n=e("./Utils");t=function(){function t(){}t.parse=function(n,e,t){if(e==null){e=false}if(t==null){t=null}return(new r).parse(n,e,t)};t.parseFile=function(s,e,t,i){var r;if(e==null){e=null}if(t==null){t=false}if(i==null){i=null}if(e!=null){return n.getStringFromFile(s,function(n){return function(s){var r;r=null;if(s!=null){r=n.parse(s,t,i)}e(r)}}(this))}else{r=n.getStringFromFile(s);if(r!=null){return this.parse(r,t,i)}return null}};t.dump=function(l,e,t,n,r){var s;if(e==null){e=2}if(t==null){t=4}if(n==null){n=false}if(r==null){r=null}s=new i;s.indentation=t;return s.dump(l,e,0,n,r)};t.register=function(){var t;t=function(e,t){return e.exports=YAML.parseFile(t)};if((typeof e!=="undefined"&&e!==null?e.extensions:void 0)!=null){e.extensions[".yml"]=t;return e.extensions[".yaml"]=t}};t.stringify=function(e,t,n,i,r){return this.dump(e,t,n,i,r)};t.load=function(e,t,n,i){return this.parseFile(e,t,n,i)};return t}();if(typeof window!=="undefined"&&window!==null){window.YAML=t}if(typeof window==="undefined"||window===null){this.YAML=t}s.exports=t},{"./Dumper":1,"./Parser":6,"./Utils":9}]},{},[10]);
diff --git a/src/main/webapp/scripts/lib/yamljs/index.js b/src/main/webapp/scripts/lib/yamljs/index.js
new file mode 100755
index 0000000000000000000000000000000000000000..d9643d85a184a4cb14721b10228631cd6174cae8
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/index.js
@@ -0,0 +1,3 @@
+
+var Yaml = require('./lib/Yaml');
+module.exports = Yaml;
diff --git a/src/main/webapp/scripts/lib/yamljs/lib/Dumper.js b/src/main/webapp/scripts/lib/yamljs/lib/Dumper.js
new file mode 100755
index 0000000000000000000000000000000000000000..13ae5eb6c96621ba60263540516daddc7a41679f
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/lib/Dumper.js
@@ -0,0 +1,53 @@
+// Generated by CoffeeScript 1.10.0
+var Dumper, Inline, Utils;
+
+Utils = require('./Utils');
+
+Inline = require('./Inline');
+
+Dumper = (function() {
+  function Dumper() {}
+
+  Dumper.indentation = 4;
+
+  Dumper.prototype.dump = function(input, inline, indent, exceptionOnInvalidType, objectEncoder) {
+    var i, key, len, output, prefix, value, willBeInlined;
+    if (inline == null) {
+      inline = 0;
+    }
+    if (indent == null) {
+      indent = 0;
+    }
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectEncoder == null) {
+      objectEncoder = null;
+    }
+    output = '';
+    prefix = (indent ? Utils.strRepeat(' ', indent) : '');
+    if (inline <= 0 || typeof input !== 'object' || input instanceof Date || Utils.isEmpty(input)) {
+      output += prefix + Inline.dump(input, exceptionOnInvalidType, objectEncoder);
+    } else {
+      if (input instanceof Array) {
+        for (i = 0, len = input.length; i < len; i++) {
+          value = input[i];
+          willBeInlined = inline - 1 <= 0 || typeof value !== 'object' || Utils.isEmpty(value);
+          output += prefix + '-' + (willBeInlined ? ' ' : "\n") + this.dump(value, inline - 1, (willBeInlined ? 0 : indent + this.indentation), exceptionOnInvalidType, objectEncoder) + (willBeInlined ? "\n" : '');
+        }
+      } else {
+        for (key in input) {
+          value = input[key];
+          willBeInlined = inline - 1 <= 0 || typeof value !== 'object' || Utils.isEmpty(value);
+          output += prefix + Inline.dump(key, exceptionOnInvalidType, objectEncoder) + ':' + (willBeInlined ? ' ' : "\n") + this.dump(value, inline - 1, (willBeInlined ? 0 : indent + this.indentation), exceptionOnInvalidType, objectEncoder) + (willBeInlined ? "\n" : '');
+        }
+      }
+    }
+    return output;
+  };
+
+  return Dumper;
+
+})();
+
+module.exports = Dumper;
diff --git a/src/main/webapp/scripts/lib/yamljs/lib/Escaper.js b/src/main/webapp/scripts/lib/yamljs/lib/Escaper.js
new file mode 100755
index 0000000000000000000000000000000000000000..4cff6395f7d660104949bf9afa1b137fb754a8a3
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/lib/Escaper.js
@@ -0,0 +1,56 @@
+// Generated by CoffeeScript 1.10.0
+var Escaper, Pattern;
+
+Pattern = require('./Pattern');
+
+Escaper = (function() {
+  var ch;
+
+  function Escaper() {}
+
+  Escaper.LIST_ESCAPEES = ['\\', '\\\\', '\\"', '"', "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", (ch = String.fromCharCode)(0x0085), ch(0x00A0), ch(0x2028), ch(0x2029)];
+
+  Escaper.LIST_ESCAPED = ['\\\\', '\\"', '\\"', '\\"', "\\0", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\a", "\\b", "\\t", "\\n", "\\v", "\\f", "\\r", "\\x0e", "\\x0f", "\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17", "\\x18", "\\x19", "\\x1a", "\\e", "\\x1c", "\\x1d", "\\x1e", "\\x1f", "\\N", "\\_", "\\L", "\\P"];
+
+  Escaper.MAPPING_ESCAPEES_TO_ESCAPED = (function() {
+    var i, j, mapping, ref;
+    mapping = {};
+    for (i = j = 0, ref = Escaper.LIST_ESCAPEES.length; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
+      mapping[Escaper.LIST_ESCAPEES[i]] = Escaper.LIST_ESCAPED[i];
+    }
+    return mapping;
+  })();
+
+  Escaper.PATTERN_CHARACTERS_TO_ESCAPE = new Pattern('[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9');
+
+  Escaper.PATTERN_MAPPING_ESCAPEES = new Pattern(Escaper.LIST_ESCAPEES.join('|').split('\\').join('\\\\'));
+
+  Escaper.PATTERN_SINGLE_QUOTING = new Pattern('[\\s\'":{}[\\],&*#?]|^[-?|<>=!%@`]');
+
+  Escaper.requiresDoubleQuoting = function(value) {
+    return this.PATTERN_CHARACTERS_TO_ESCAPE.test(value);
+  };
+
+  Escaper.escapeWithDoubleQuotes = function(value) {
+    var result;
+    result = this.PATTERN_MAPPING_ESCAPEES.replace(value, (function(_this) {
+      return function(str) {
+        return _this.MAPPING_ESCAPEES_TO_ESCAPED[str];
+      };
+    })(this));
+    return '"' + result + '"';
+  };
+
+  Escaper.requiresSingleQuoting = function(value) {
+    return this.PATTERN_SINGLE_QUOTING.test(value);
+  };
+
+  Escaper.escapeWithSingleQuotes = function(value) {
+    return "'" + value.replace(/'/g, "''") + "'";
+  };
+
+  return Escaper;
+
+})();
+
+module.exports = Escaper;
diff --git a/src/main/webapp/scripts/lib/yamljs/lib/Exception/DumpException.js b/src/main/webapp/scripts/lib/yamljs/lib/Exception/DumpException.js
new file mode 100755
index 0000000000000000000000000000000000000000..2911a1def766ae45e13215f5fd21cb1f79b0e052
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/lib/Exception/DumpException.js
@@ -0,0 +1,27 @@
+// Generated by CoffeeScript 1.10.0
+var DumpException,
+  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+  hasProp = {}.hasOwnProperty;
+
+DumpException = (function(superClass) {
+  extend(DumpException, superClass);
+
+  function DumpException(message, parsedLine, snippet) {
+    this.message = message;
+    this.parsedLine = parsedLine;
+    this.snippet = snippet;
+  }
+
+  DumpException.prototype.toString = function() {
+    if ((this.parsedLine != null) && (this.snippet != null)) {
+      return '<DumpException> ' + this.message + ' (line ' + this.parsedLine + ': \'' + this.snippet + '\')';
+    } else {
+      return '<DumpException> ' + this.message;
+    }
+  };
+
+  return DumpException;
+
+})(Error);
+
+module.exports = DumpException;
diff --git a/src/main/webapp/scripts/lib/yamljs/lib/Exception/ParseException.js b/src/main/webapp/scripts/lib/yamljs/lib/Exception/ParseException.js
new file mode 100755
index 0000000000000000000000000000000000000000..61c4ae042ea45722b4fb3841a09ab52b20146b84
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/lib/Exception/ParseException.js
@@ -0,0 +1,27 @@
+// Generated by CoffeeScript 1.10.0
+var ParseException,
+  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+  hasProp = {}.hasOwnProperty;
+
+ParseException = (function(superClass) {
+  extend(ParseException, superClass);
+
+  function ParseException(message, parsedLine, snippet) {
+    this.message = message;
+    this.parsedLine = parsedLine;
+    this.snippet = snippet;
+  }
+
+  ParseException.prototype.toString = function() {
+    if ((this.parsedLine != null) && (this.snippet != null)) {
+      return '<ParseException> ' + this.message + ' (line ' + this.parsedLine + ': \'' + this.snippet + '\')';
+    } else {
+      return '<ParseException> ' + this.message;
+    }
+  };
+
+  return ParseException;
+
+})(Error);
+
+module.exports = ParseException;
diff --git a/src/main/webapp/scripts/lib/yamljs/lib/Inline.js b/src/main/webapp/scripts/lib/yamljs/lib/Inline.js
new file mode 100755
index 0000000000000000000000000000000000000000..9006110e5644699614e5dba8920d0d6cad9cf274
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/lib/Inline.js
@@ -0,0 +1,483 @@
+// Generated by CoffeeScript 1.10.0
+var DumpException, Escaper, Inline, ParseException, Pattern, Unescaper, Utils,
+  indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+
+Pattern = require('./Pattern');
+
+Unescaper = require('./Unescaper');
+
+Escaper = require('./Escaper');
+
+Utils = require('./Utils');
+
+ParseException = require('./Exception/ParseException');
+
+DumpException = require('./Exception/DumpException');
+
+Inline = (function() {
+  function Inline() {}
+
+  Inline.REGEX_QUOTED_STRING = '(?:"(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\']*(?:\'\'[^\']*)*)\')';
+
+  Inline.PATTERN_TRAILING_COMMENTS = new Pattern('^\\s*#.*$');
+
+  Inline.PATTERN_QUOTED_SCALAR = new Pattern('^' + Inline.REGEX_QUOTED_STRING);
+
+  Inline.PATTERN_THOUSAND_NUMERIC_SCALAR = new Pattern('^(-|\\+)?[0-9,]+(\\.[0-9]+)?$');
+
+  Inline.PATTERN_SCALAR_BY_DELIMITERS = {};
+
+  Inline.settings = {};
+
+  Inline.configure = function(exceptionOnInvalidType, objectDecoder) {
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = null;
+    }
+    if (objectDecoder == null) {
+      objectDecoder = null;
+    }
+    this.settings.exceptionOnInvalidType = exceptionOnInvalidType;
+    this.settings.objectDecoder = objectDecoder;
+  };
+
+  Inline.parse = function(value, exceptionOnInvalidType, objectDecoder) {
+    var context, result;
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectDecoder == null) {
+      objectDecoder = null;
+    }
+    this.settings.exceptionOnInvalidType = exceptionOnInvalidType;
+    this.settings.objectDecoder = objectDecoder;
+    if (value == null) {
+      return '';
+    }
+    value = Utils.trim(value);
+    if (0 === value.length) {
+      return '';
+    }
+    context = {
+      exceptionOnInvalidType: exceptionOnInvalidType,
+      objectDecoder: objectDecoder,
+      i: 0
+    };
+    switch (value.charAt(0)) {
+      case '[':
+        result = this.parseSequence(value, context);
+        ++context.i;
+        break;
+      case '{':
+        result = this.parseMapping(value, context);
+        ++context.i;
+        break;
+      default:
+        result = this.parseScalar(value, null, ['"', "'"], context);
+    }
+    if (this.PATTERN_TRAILING_COMMENTS.replace(value.slice(context.i), '') !== '') {
+      throw new ParseException('Unexpected characters near "' + value.slice(context.i) + '".');
+    }
+    return result;
+  };
+
+  Inline.dump = function(value, exceptionOnInvalidType, objectEncoder) {
+    var ref, result, type;
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectEncoder == null) {
+      objectEncoder = null;
+    }
+    if (value == null) {
+      return 'null';
+    }
+    type = typeof value;
+    if (type === 'object') {
+      if (value instanceof Date) {
+        return value.toISOString();
+      } else if (objectEncoder != null) {
+        result = objectEncoder(value);
+        if (typeof result === 'string' || (result != null)) {
+          return result;
+        }
+      }
+      return this.dumpObject(value);
+    }
+    if (type === 'boolean') {
+      return (value ? 'true' : 'false');
+    }
+    if (Utils.isDigits(value)) {
+      return (type === 'string' ? "'" + value + "'" : String(parseInt(value)));
+    }
+    if (Utils.isNumeric(value)) {
+      return (type === 'string' ? "'" + value + "'" : String(parseFloat(value)));
+    }
+    if (type === 'number') {
+      return (value === Infinity ? '.Inf' : (value === -Infinity ? '-.Inf' : (isNaN(value) ? '.NaN' : value)));
+    }
+    if (Escaper.requiresDoubleQuoting(value)) {
+      return Escaper.escapeWithDoubleQuotes(value);
+    }
+    if (Escaper.requiresSingleQuoting(value)) {
+      return Escaper.escapeWithSingleQuotes(value);
+    }
+    if ('' === value) {
+      return '""';
+    }
+    if (Utils.PATTERN_DATE.test(value)) {
+      return "'" + value + "'";
+    }
+    if ((ref = value.toLowerCase()) === 'null' || ref === '~' || ref === 'true' || ref === 'false') {
+      return "'" + value + "'";
+    }
+    return value;
+  };
+
+  Inline.dumpObject = function(value, exceptionOnInvalidType, objectSupport) {
+    var j, key, len1, output, val;
+    if (objectSupport == null) {
+      objectSupport = null;
+    }
+    if (value instanceof Array) {
+      output = [];
+      for (j = 0, len1 = value.length; j < len1; j++) {
+        val = value[j];
+        output.push(this.dump(val));
+      }
+      return '[' + output.join(', ') + ']';
+    } else {
+      output = [];
+      for (key in value) {
+        val = value[key];
+        output.push(this.dump(key) + ': ' + this.dump(val));
+      }
+      return '{' + output.join(', ') + '}';
+    }
+  };
+
+  Inline.parseScalar = function(scalar, delimiters, stringDelimiters, context, evaluate) {
+    var i, joinedDelimiters, match, output, pattern, ref, ref1, strpos, tmp;
+    if (delimiters == null) {
+      delimiters = null;
+    }
+    if (stringDelimiters == null) {
+      stringDelimiters = ['"', "'"];
+    }
+    if (context == null) {
+      context = null;
+    }
+    if (evaluate == null) {
+      evaluate = true;
+    }
+    if (context == null) {
+      context = {
+        exceptionOnInvalidType: this.settings.exceptionOnInvalidType,
+        objectDecoder: this.settings.objectDecoder,
+        i: 0
+      };
+    }
+    i = context.i;
+    if (ref = scalar.charAt(i), indexOf.call(stringDelimiters, ref) >= 0) {
+      output = this.parseQuotedScalar(scalar, context);
+      i = context.i;
+      if (delimiters != null) {
+        tmp = Utils.ltrim(scalar.slice(i), ' ');
+        if (!(ref1 = tmp.charAt(0), indexOf.call(delimiters, ref1) >= 0)) {
+          throw new ParseException('Unexpected characters (' + scalar.slice(i) + ').');
+        }
+      }
+    } else {
+      if (!delimiters) {
+        output = scalar.slice(i);
+        i += output.length;
+        strpos = output.indexOf(' #');
+        if (strpos !== -1) {
+          output = Utils.rtrim(output.slice(0, strpos));
+        }
+      } else {
+        joinedDelimiters = delimiters.join('|');
+        pattern = this.PATTERN_SCALAR_BY_DELIMITERS[joinedDelimiters];
+        if (pattern == null) {
+          pattern = new Pattern('^(.+?)(' + joinedDelimiters + ')');
+          this.PATTERN_SCALAR_BY_DELIMITERS[joinedDelimiters] = pattern;
+        }
+        if (match = pattern.exec(scalar.slice(i))) {
+          output = match[1];
+          i += output.length;
+        } else {
+          throw new ParseException('Malformed inline YAML string (' + scalar + ').');
+        }
+      }
+      if (evaluate) {
+        output = this.evaluateScalar(output, context);
+      }
+    }
+    context.i = i;
+    return output;
+  };
+
+  Inline.parseQuotedScalar = function(scalar, context) {
+    var i, match, output;
+    i = context.i;
+    if (!(match = this.PATTERN_QUOTED_SCALAR.exec(scalar.slice(i)))) {
+      throw new ParseException('Malformed inline YAML string (' + scalar.slice(i) + ').');
+    }
+    output = match[0].substr(1, match[0].length - 2);
+    if ('"' === scalar.charAt(i)) {
+      output = Unescaper.unescapeDoubleQuotedString(output);
+    } else {
+      output = Unescaper.unescapeSingleQuotedString(output);
+    }
+    i += match[0].length;
+    context.i = i;
+    return output;
+  };
+
+  Inline.parseSequence = function(sequence, context) {
+    var e, error, i, isQuoted, len, output, ref, value;
+    output = [];
+    len = sequence.length;
+    i = context.i;
+    i += 1;
+    while (i < len) {
+      context.i = i;
+      switch (sequence.charAt(i)) {
+        case '[':
+          output.push(this.parseSequence(sequence, context));
+          i = context.i;
+          break;
+        case '{':
+          output.push(this.parseMapping(sequence, context));
+          i = context.i;
+          break;
+        case ']':
+          return output;
+        case ',':
+        case ' ':
+        case "\n":
+          break;
+        default:
+          isQuoted = ((ref = sequence.charAt(i)) === '"' || ref === "'");
+          value = this.parseScalar(sequence, [',', ']'], ['"', "'"], context);
+          i = context.i;
+          if (!isQuoted && typeof value === 'string' && (value.indexOf(': ') !== -1 || value.indexOf(":\n") !== -1)) {
+            try {
+              value = this.parseMapping('{' + value + '}');
+            } catch (error) {
+              e = error;
+            }
+          }
+          output.push(value);
+          --i;
+      }
+      ++i;
+    }
+    throw new ParseException('Malformed inline YAML string ' + sequence);
+  };
+
+  Inline.parseMapping = function(mapping, context) {
+    var done, i, key, len, output, shouldContinueWhileLoop, value;
+    output = {};
+    len = mapping.length;
+    i = context.i;
+    i += 1;
+    shouldContinueWhileLoop = false;
+    while (i < len) {
+      context.i = i;
+      switch (mapping.charAt(i)) {
+        case ' ':
+        case ',':
+        case "\n":
+          ++i;
+          context.i = i;
+          shouldContinueWhileLoop = true;
+          break;
+        case '}':
+          return output;
+      }
+      if (shouldContinueWhileLoop) {
+        shouldContinueWhileLoop = false;
+        continue;
+      }
+      key = this.parseScalar(mapping, [':', ' ', "\n"], ['"', "'"], context, false);
+      i = context.i;
+      done = false;
+      while (i < len) {
+        context.i = i;
+        switch (mapping.charAt(i)) {
+          case '[':
+            value = this.parseSequence(mapping, context);
+            i = context.i;
+            if (output[key] === void 0) {
+              output[key] = value;
+            }
+            done = true;
+            break;
+          case '{':
+            value = this.parseMapping(mapping, context);
+            i = context.i;
+            if (output[key] === void 0) {
+              output[key] = value;
+            }
+            done = true;
+            break;
+          case ':':
+          case ' ':
+          case "\n":
+            break;
+          default:
+            value = this.parseScalar(mapping, [',', '}'], ['"', "'"], context);
+            i = context.i;
+            if (output[key] === void 0) {
+              output[key] = value;
+            }
+            done = true;
+            --i;
+        }
+        ++i;
+        if (done) {
+          break;
+        }
+      }
+    }
+    throw new ParseException('Malformed inline YAML string ' + mapping);
+  };
+
+  Inline.evaluateScalar = function(scalar, context) {
+    var cast, date, exceptionOnInvalidType, firstChar, firstSpace, firstWord, objectDecoder, raw, scalarLower, subValue, trimmedScalar;
+    scalar = Utils.trim(scalar);
+    scalarLower = scalar.toLowerCase();
+    switch (scalarLower) {
+      case 'null':
+      case '':
+      case '~':
+        return null;
+      case 'true':
+        return true;
+      case 'false':
+        return false;
+      case '.inf':
+        return Infinity;
+      case '.nan':
+        return NaN;
+      case '-.inf':
+        return Infinity;
+      default:
+        firstChar = scalarLower.charAt(0);
+        switch (firstChar) {
+          case '!':
+            firstSpace = scalar.indexOf(' ');
+            if (firstSpace === -1) {
+              firstWord = scalarLower;
+            } else {
+              firstWord = scalarLower.slice(0, firstSpace);
+            }
+            switch (firstWord) {
+              case '!':
+                if (firstSpace !== -1) {
+                  return parseInt(this.parseScalar(scalar.slice(2)));
+                }
+                return null;
+              case '!str':
+                return Utils.ltrim(scalar.slice(4));
+              case '!!str':
+                return Utils.ltrim(scalar.slice(5));
+              case '!!int':
+                return parseInt(this.parseScalar(scalar.slice(5)));
+              case '!!bool':
+                return Utils.parseBoolean(this.parseScalar(scalar.slice(6)), false);
+              case '!!float':
+                return parseFloat(this.parseScalar(scalar.slice(7)));
+              case '!!timestamp':
+                return Utils.stringToDate(Utils.ltrim(scalar.slice(11)));
+              default:
+                if (context == null) {
+                  context = {
+                    exceptionOnInvalidType: this.settings.exceptionOnInvalidType,
+                    objectDecoder: this.settings.objectDecoder,
+                    i: 0
+                  };
+                }
+                objectDecoder = context.objectDecoder, exceptionOnInvalidType = context.exceptionOnInvalidType;
+                if (objectDecoder) {
+                  trimmedScalar = Utils.rtrim(scalar);
+                  firstSpace = trimmedScalar.indexOf(' ');
+                  if (firstSpace === -1) {
+                    return objectDecoder(trimmedScalar, null);
+                  } else {
+                    subValue = Utils.ltrim(trimmedScalar.slice(firstSpace + 1));
+                    if (!(subValue.length > 0)) {
+                      subValue = null;
+                    }
+                    return objectDecoder(trimmedScalar.slice(0, firstSpace), subValue);
+                  }
+                }
+                if (exceptionOnInvalidType) {
+                  throw new ParseException('Custom object support when parsing a YAML file has been disabled.');
+                }
+                return null;
+            }
+            break;
+          case '0':
+            if ('0x' === scalar.slice(0, 2)) {
+              return Utils.hexDec(scalar);
+            } else if (Utils.isDigits(scalar)) {
+              return Utils.octDec(scalar);
+            } else if (Utils.isNumeric(scalar)) {
+              return parseFloat(scalar);
+            } else {
+              return scalar;
+            }
+            break;
+          case '+':
+            if (Utils.isDigits(scalar)) {
+              raw = scalar;
+              cast = parseInt(raw);
+              if (raw === String(cast)) {
+                return cast;
+              } else {
+                return raw;
+              }
+            } else if (Utils.isNumeric(scalar)) {
+              return parseFloat(scalar);
+            } else if (this.PATTERN_THOUSAND_NUMERIC_SCALAR.test(scalar)) {
+              return parseFloat(scalar.replace(',', ''));
+            }
+            return scalar;
+          case '-':
+            if (Utils.isDigits(scalar.slice(1))) {
+              if ('0' === scalar.charAt(1)) {
+                return -Utils.octDec(scalar.slice(1));
+              } else {
+                raw = scalar.slice(1);
+                cast = parseInt(raw);
+                if (raw === String(cast)) {
+                  return -cast;
+                } else {
+                  return -raw;
+                }
+              }
+            } else if (Utils.isNumeric(scalar)) {
+              return parseFloat(scalar);
+            } else if (this.PATTERN_THOUSAND_NUMERIC_SCALAR.test(scalar)) {
+              return parseFloat(scalar.replace(',', ''));
+            }
+            return scalar;
+          default:
+            if (date = Utils.stringToDate(scalar)) {
+              return date;
+            } else if (Utils.isNumeric(scalar)) {
+              return parseFloat(scalar);
+            } else if (this.PATTERN_THOUSAND_NUMERIC_SCALAR.test(scalar)) {
+              return parseFloat(scalar.replace(',', ''));
+            }
+            return scalar;
+        }
+    }
+  };
+
+  return Inline;
+
+})();
+
+module.exports = Inline;
diff --git a/src/main/webapp/scripts/lib/yamljs/lib/Parser.js b/src/main/webapp/scripts/lib/yamljs/lib/Parser.js
new file mode 100755
index 0000000000000000000000000000000000000000..a1df07c7cae0f87aad2d6a82742d1690b0381a14
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/lib/Parser.js
@@ -0,0 +1,602 @@
+// Generated by CoffeeScript 1.10.0
+var Inline, ParseException, Parser, Pattern, Utils;
+
+Inline = require('./Inline');
+
+Pattern = require('./Pattern');
+
+Utils = require('./Utils');
+
+ParseException = require('./Exception/ParseException');
+
+Parser = (function() {
+  Parser.prototype.PATTERN_FOLDED_SCALAR_ALL = new Pattern('^(?:(?<type>![^\\|>]*)\\s+)?(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$');
+
+  Parser.prototype.PATTERN_FOLDED_SCALAR_END = new Pattern('(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$');
+
+  Parser.prototype.PATTERN_SEQUENCE_ITEM = new Pattern('^\\-((?<leadspaces>\\s+)(?<value>.+?))?\\s*$');
+
+  Parser.prototype.PATTERN_ANCHOR_VALUE = new Pattern('^&(?<ref>[^ ]+) *(?<value>.*)');
+
+  Parser.prototype.PATTERN_COMPACT_NOTATION = new Pattern('^(?<key>' + Inline.REGEX_QUOTED_STRING + '|[^ \'"\\{\\[].*?) *\\:(\\s+(?<value>.+?))?\\s*$');
+
+  Parser.prototype.PATTERN_MAPPING_ITEM = new Pattern('^(?<key>' + Inline.REGEX_QUOTED_STRING + '|[^ \'"\\[\\{].*?) *\\:(\\s+(?<value>.+?))?\\s*$');
+
+  Parser.prototype.PATTERN_DECIMAL = new Pattern('\\d+');
+
+  Parser.prototype.PATTERN_INDENT_SPACES = new Pattern('^ +');
+
+  Parser.prototype.PATTERN_TRAILING_LINES = new Pattern('(\n*)$');
+
+  Parser.prototype.PATTERN_YAML_HEADER = new Pattern('^\\%YAML[: ][\\d\\.]+.*\n');
+
+  Parser.prototype.PATTERN_LEADING_COMMENTS = new Pattern('^(\\#.*?\n)+');
+
+  Parser.prototype.PATTERN_DOCUMENT_MARKER_START = new Pattern('^\\-\\-\\-.*?\n');
+
+  Parser.prototype.PATTERN_DOCUMENT_MARKER_END = new Pattern('^\\.\\.\\.\\s*$');
+
+  Parser.prototype.PATTERN_FOLDED_SCALAR_BY_INDENTATION = {};
+
+  Parser.prototype.CONTEXT_NONE = 0;
+
+  Parser.prototype.CONTEXT_SEQUENCE = 1;
+
+  Parser.prototype.CONTEXT_MAPPING = 2;
+
+  function Parser(offset) {
+    this.offset = offset != null ? offset : 0;
+    this.lines = [];
+    this.currentLineNb = -1;
+    this.currentLine = '';
+    this.refs = {};
+  }
+
+  Parser.prototype.parse = function(value, exceptionOnInvalidType, objectDecoder) {
+    var alias, allowOverwrite, block, c, context, data, e, error, error1, error2, first, i, indent, isRef, j, k, key, l, lastKey, len, len1, len2, len3, lineCount, m, matches, mergeNode, n, name, parsed, parsedItem, parser, ref, ref1, ref2, refName, refValue, val, values;
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectDecoder == null) {
+      objectDecoder = null;
+    }
+    this.currentLineNb = -1;
+    this.currentLine = '';
+    this.lines = this.cleanup(value).split("\n");
+    data = null;
+    context = this.CONTEXT_NONE;
+    allowOverwrite = false;
+    while (this.moveToNextLine()) {
+      if (this.isCurrentLineEmpty()) {
+        continue;
+      }
+      if ("\t" === this.currentLine[0]) {
+        throw new ParseException('A YAML file cannot contain tabs as indentation.', this.getRealCurrentLineNb() + 1, this.currentLine);
+      }
+      isRef = mergeNode = false;
+      if (values = this.PATTERN_SEQUENCE_ITEM.exec(this.currentLine)) {
+        if (this.CONTEXT_MAPPING === context) {
+          throw new ParseException('You cannot define a sequence item when in a mapping');
+        }
+        context = this.CONTEXT_SEQUENCE;
+        if (data == null) {
+          data = [];
+        }
+        if ((values.value != null) && (matches = this.PATTERN_ANCHOR_VALUE.exec(values.value))) {
+          isRef = matches.ref;
+          values.value = matches.value;
+        }
+        if (!(values.value != null) || '' === Utils.trim(values.value, ' ') || Utils.ltrim(values.value, ' ').indexOf('#') === 0) {
+          if (this.currentLineNb < this.lines.length - 1 && !this.isNextLineUnIndentedCollection()) {
+            c = this.getRealCurrentLineNb() + 1;
+            parser = new Parser(c);
+            parser.refs = this.refs;
+            data.push(parser.parse(this.getNextEmbedBlock(null, true), exceptionOnInvalidType, objectDecoder));
+          } else {
+            data.push(null);
+          }
+        } else {
+          if (((ref = values.leadspaces) != null ? ref.length : void 0) && (matches = this.PATTERN_COMPACT_NOTATION.exec(values.value))) {
+            c = this.getRealCurrentLineNb();
+            parser = new Parser(c);
+            parser.refs = this.refs;
+            block = values.value;
+            indent = this.getCurrentLineIndentation();
+            if (this.isNextLineIndented(false)) {
+              block += "\n" + this.getNextEmbedBlock(indent + values.leadspaces.length + 1, true);
+            }
+            data.push(parser.parse(block, exceptionOnInvalidType, objectDecoder));
+          } else {
+            data.push(this.parseValue(values.value, exceptionOnInvalidType, objectDecoder));
+          }
+        }
+      } else if ((values = this.PATTERN_MAPPING_ITEM.exec(this.currentLine)) && values.key.indexOf(' #') === -1) {
+        if (this.CONTEXT_SEQUENCE === context) {
+          throw new ParseException('You cannot define a mapping item when in a sequence');
+        }
+        context = this.CONTEXT_MAPPING;
+        if (data == null) {
+          data = {};
+        }
+        Inline.configure(exceptionOnInvalidType, objectDecoder);
+        try {
+          key = Inline.parseScalar(values.key);
+        } catch (error) {
+          e = error;
+          e.parsedLine = this.getRealCurrentLineNb() + 1;
+          e.snippet = this.currentLine;
+          throw e;
+        }
+        if ('<<' === key) {
+          mergeNode = true;
+          allowOverwrite = true;
+          if (((ref1 = values.value) != null ? ref1.indexOf('*') : void 0) === 0) {
+            refName = values.value.slice(1);
+            if (this.refs[refName] == null) {
+              throw new ParseException('Reference "' + refName + '" does not exist.', this.getRealCurrentLineNb() + 1, this.currentLine);
+            }
+            refValue = this.refs[refName];
+            if (typeof refValue !== 'object') {
+              throw new ParseException('YAML merge keys used with a scalar value instead of an object.', this.getRealCurrentLineNb() + 1, this.currentLine);
+            }
+            if (refValue instanceof Array) {
+              for (i = j = 0, len = refValue.length; j < len; i = ++j) {
+                value = refValue[i];
+                if (data[name = String(i)] == null) {
+                  data[name] = value;
+                }
+              }
+            } else {
+              for (key in refValue) {
+                value = refValue[key];
+                if (data[key] == null) {
+                  data[key] = value;
+                }
+              }
+            }
+          } else {
+            if ((values.value != null) && values.value !== '') {
+              value = values.value;
+            } else {
+              value = this.getNextEmbedBlock();
+            }
+            c = this.getRealCurrentLineNb() + 1;
+            parser = new Parser(c);
+            parser.refs = this.refs;
+            parsed = parser.parse(value, exceptionOnInvalidType);
+            if (typeof parsed !== 'object') {
+              throw new ParseException('YAML merge keys used with a scalar value instead of an object.', this.getRealCurrentLineNb() + 1, this.currentLine);
+            }
+            if (parsed instanceof Array) {
+              for (l = 0, len1 = parsed.length; l < len1; l++) {
+                parsedItem = parsed[l];
+                if (typeof parsedItem !== 'object') {
+                  throw new ParseException('Merge items must be objects.', this.getRealCurrentLineNb() + 1, parsedItem);
+                }
+                if (parsedItem instanceof Array) {
+                  for (i = m = 0, len2 = parsedItem.length; m < len2; i = ++m) {
+                    value = parsedItem[i];
+                    k = String(i);
+                    if (!data.hasOwnProperty(k)) {
+                      data[k] = value;
+                    }
+                  }
+                } else {
+                  for (key in parsedItem) {
+                    value = parsedItem[key];
+                    if (!data.hasOwnProperty(key)) {
+                      data[key] = value;
+                    }
+                  }
+                }
+              }
+            } else {
+              for (key in parsed) {
+                value = parsed[key];
+                if (!data.hasOwnProperty(key)) {
+                  data[key] = value;
+                }
+              }
+            }
+          }
+        } else if ((values.value != null) && (matches = this.PATTERN_ANCHOR_VALUE.exec(values.value))) {
+          isRef = matches.ref;
+          values.value = matches.value;
+        }
+        if (mergeNode) {
+
+        } else if (!(values.value != null) || '' === Utils.trim(values.value, ' ') || Utils.ltrim(values.value, ' ').indexOf('#') === 0) {
+          if (!(this.isNextLineIndented()) && !(this.isNextLineUnIndentedCollection())) {
+            if (allowOverwrite || data[key] === void 0) {
+              data[key] = null;
+            }
+          } else {
+            c = this.getRealCurrentLineNb() + 1;
+            parser = new Parser(c);
+            parser.refs = this.refs;
+            val = parser.parse(this.getNextEmbedBlock(), exceptionOnInvalidType, objectDecoder);
+            if (allowOverwrite || data[key] === void 0) {
+              data[key] = val;
+            }
+          }
+        } else {
+          val = this.parseValue(values.value, exceptionOnInvalidType, objectDecoder);
+          if (allowOverwrite || data[key] === void 0) {
+            data[key] = val;
+          }
+        }
+      } else {
+        lineCount = this.lines.length;
+        if (1 === lineCount || (2 === lineCount && Utils.isEmpty(this.lines[1]))) {
+          try {
+            value = Inline.parse(this.lines[0], exceptionOnInvalidType, objectDecoder);
+          } catch (error1) {
+            e = error1;
+            e.parsedLine = this.getRealCurrentLineNb() + 1;
+            e.snippet = this.currentLine;
+            throw e;
+          }
+          if (typeof value === 'object') {
+            if (value instanceof Array) {
+              first = value[0];
+            } else {
+              for (key in value) {
+                first = value[key];
+                break;
+              }
+            }
+            if (typeof first === 'string' && first.indexOf('*') === 0) {
+              data = [];
+              for (n = 0, len3 = value.length; n < len3; n++) {
+                alias = value[n];
+                data.push(this.refs[alias.slice(1)]);
+              }
+              value = data;
+            }
+          }
+          return value;
+        } else if ((ref2 = Utils.ltrim(value).charAt(0)) === '[' || ref2 === '{') {
+          try {
+            return Inline.parse(value, exceptionOnInvalidType, objectDecoder);
+          } catch (error2) {
+            e = error2;
+            e.parsedLine = this.getRealCurrentLineNb() + 1;
+            e.snippet = this.currentLine;
+            throw e;
+          }
+        }
+        throw new ParseException('Unable to parse.', this.getRealCurrentLineNb() + 1, this.currentLine);
+      }
+      if (isRef) {
+        if (data instanceof Array) {
+          this.refs[isRef] = data[data.length - 1];
+        } else {
+          lastKey = null;
+          for (key in data) {
+            lastKey = key;
+          }
+          this.refs[isRef] = data[lastKey];
+        }
+      }
+    }
+    if (Utils.isEmpty(data)) {
+      return null;
+    } else {
+      return data;
+    }
+  };
+
+  Parser.prototype.getRealCurrentLineNb = function() {
+    return this.currentLineNb + this.offset;
+  };
+
+  Parser.prototype.getCurrentLineIndentation = function() {
+    return this.currentLine.length - Utils.ltrim(this.currentLine, ' ').length;
+  };
+
+  Parser.prototype.getNextEmbedBlock = function(indentation, includeUnindentedCollection) {
+    var data, indent, isItUnindentedCollection, newIndent, removeComments, removeCommentsPattern, unindentedEmbedBlock;
+    if (indentation == null) {
+      indentation = null;
+    }
+    if (includeUnindentedCollection == null) {
+      includeUnindentedCollection = false;
+    }
+    this.moveToNextLine();
+    if (indentation == null) {
+      newIndent = this.getCurrentLineIndentation();
+      unindentedEmbedBlock = this.isStringUnIndentedCollectionItem(this.currentLine);
+      if (!(this.isCurrentLineEmpty()) && 0 === newIndent && !unindentedEmbedBlock) {
+        throw new ParseException('Indentation problem.', this.getRealCurrentLineNb() + 1, this.currentLine);
+      }
+    } else {
+      newIndent = indentation;
+    }
+    data = [this.currentLine.slice(newIndent)];
+    if (!includeUnindentedCollection) {
+      isItUnindentedCollection = this.isStringUnIndentedCollectionItem(this.currentLine);
+    }
+    removeCommentsPattern = this.PATTERN_FOLDED_SCALAR_END;
+    removeComments = !removeCommentsPattern.test(this.currentLine);
+    while (this.moveToNextLine()) {
+      indent = this.getCurrentLineIndentation();
+      if (indent === newIndent) {
+        removeComments = !removeCommentsPattern.test(this.currentLine);
+      }
+      if (isItUnindentedCollection && !this.isStringUnIndentedCollectionItem(this.currentLine) && indent === newIndent) {
+        this.moveToPreviousLine();
+        break;
+      }
+      if (this.isCurrentLineBlank()) {
+        data.push(this.currentLine.slice(newIndent));
+        continue;
+      }
+      if (removeComments && this.isCurrentLineComment()) {
+        if (indent === newIndent) {
+          continue;
+        }
+      }
+      if (indent >= newIndent) {
+        data.push(this.currentLine.slice(newIndent));
+      } else if (Utils.ltrim(this.currentLine).charAt(0) === '#') {
+
+      } else if (0 === indent) {
+        this.moveToPreviousLine();
+        break;
+      } else {
+        throw new ParseException('Indentation problem.', this.getRealCurrentLineNb() + 1, this.currentLine);
+      }
+    }
+    return data.join("\n");
+  };
+
+  Parser.prototype.moveToNextLine = function() {
+    if (this.currentLineNb >= this.lines.length - 1) {
+      return false;
+    }
+    this.currentLine = this.lines[++this.currentLineNb];
+    return true;
+  };
+
+  Parser.prototype.moveToPreviousLine = function() {
+    this.currentLine = this.lines[--this.currentLineNb];
+  };
+
+  Parser.prototype.parseValue = function(value, exceptionOnInvalidType, objectDecoder) {
+    var e, error, error1, foldedIndent, matches, modifiers, pos, ref, ref1, val;
+    if (0 === value.indexOf('*')) {
+      pos = value.indexOf('#');
+      if (pos !== -1) {
+        value = value.substr(1, pos - 2);
+      } else {
+        value = value.slice(1);
+      }
+      if (this.refs[value] === void 0) {
+        throw new ParseException('Reference "' + value + '" does not exist.', this.currentLine);
+      }
+      return this.refs[value];
+    }
+    if (matches = this.PATTERN_FOLDED_SCALAR_ALL.exec(value)) {
+      modifiers = (ref = matches.modifiers) != null ? ref : '';
+      foldedIndent = Math.abs(parseInt(modifiers));
+      if (isNaN(foldedIndent)) {
+        foldedIndent = 0;
+      }
+      val = this.parseFoldedScalar(matches.separator, this.PATTERN_DECIMAL.replace(modifiers, ''), foldedIndent);
+      if (matches.type != null) {
+        Inline.configure(exceptionOnInvalidType, objectDecoder);
+        return Inline.parseScalar(matches.type + ' ' + val);
+      } else {
+        return val;
+      }
+    }
+    try {
+      return Inline.parse(value, exceptionOnInvalidType, objectDecoder);
+    } catch (error) {
+      e = error;
+      if (((ref1 = value.charAt(0)) === '[' || ref1 === '{') && e instanceof ParseException && this.isNextLineIndented()) {
+        value += "\n" + this.getNextEmbedBlock();
+        try {
+          return Inline.parse(value, exceptionOnInvalidType, objectDecoder);
+        } catch (error1) {
+          e = error1;
+          e.parsedLine = this.getRealCurrentLineNb() + 1;
+          e.snippet = this.currentLine;
+          throw e;
+        }
+      } else {
+        e.parsedLine = this.getRealCurrentLineNb() + 1;
+        e.snippet = this.currentLine;
+        throw e;
+      }
+    }
+  };
+
+  Parser.prototype.parseFoldedScalar = function(separator, indicator, indentation) {
+    var isCurrentLineBlank, j, len, line, matches, newText, notEOF, pattern, ref, text;
+    if (indicator == null) {
+      indicator = '';
+    }
+    if (indentation == null) {
+      indentation = 0;
+    }
+    notEOF = this.moveToNextLine();
+    if (!notEOF) {
+      return '';
+    }
+    isCurrentLineBlank = this.isCurrentLineBlank();
+    text = '';
+    while (notEOF && isCurrentLineBlank) {
+      if (notEOF = this.moveToNextLine()) {
+        text += "\n";
+        isCurrentLineBlank = this.isCurrentLineBlank();
+      }
+    }
+    if (0 === indentation) {
+      if (matches = this.PATTERN_INDENT_SPACES.exec(this.currentLine)) {
+        indentation = matches[0].length;
+      }
+    }
+    if (indentation > 0) {
+      pattern = this.PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation];
+      if (pattern == null) {
+        pattern = new Pattern('^ {' + indentation + '}(.*)$');
+        Parser.prototype.PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation] = pattern;
+      }
+      while (notEOF && (isCurrentLineBlank || (matches = pattern.exec(this.currentLine)))) {
+        if (isCurrentLineBlank) {
+          text += this.currentLine.slice(indentation);
+        } else {
+          text += matches[1];
+        }
+        if (notEOF = this.moveToNextLine()) {
+          text += "\n";
+          isCurrentLineBlank = this.isCurrentLineBlank();
+        }
+      }
+    } else if (notEOF) {
+      text += "\n";
+    }
+    if (notEOF) {
+      this.moveToPreviousLine();
+    }
+    if ('>' === separator) {
+      newText = '';
+      ref = text.split("\n");
+      for (j = 0, len = ref.length; j < len; j++) {
+        line = ref[j];
+        if (line.length === 0 || line.charAt(0) === ' ') {
+          newText = Utils.rtrim(newText, ' ') + line + "\n";
+        } else {
+          newText += line + ' ';
+        }
+      }
+      text = newText;
+    }
+    if ('+' !== indicator) {
+      text = Utils.rtrim(text);
+    }
+    if ('' === indicator) {
+      text = this.PATTERN_TRAILING_LINES.replace(text, "\n");
+    } else if ('-' === indicator) {
+      text = this.PATTERN_TRAILING_LINES.replace(text, '');
+    }
+    return text;
+  };
+
+  Parser.prototype.isNextLineIndented = function(ignoreComments) {
+    var EOF, currentIndentation, ret;
+    if (ignoreComments == null) {
+      ignoreComments = true;
+    }
+    currentIndentation = this.getCurrentLineIndentation();
+    EOF = !this.moveToNextLine();
+    if (ignoreComments) {
+      while (!EOF && this.isCurrentLineEmpty()) {
+        EOF = !this.moveToNextLine();
+      }
+    } else {
+      while (!EOF && this.isCurrentLineBlank()) {
+        EOF = !this.moveToNextLine();
+      }
+    }
+    if (EOF) {
+      return false;
+    }
+    ret = false;
+    if (this.getCurrentLineIndentation() > currentIndentation) {
+      ret = true;
+    }
+    this.moveToPreviousLine();
+    return ret;
+  };
+
+  Parser.prototype.isCurrentLineEmpty = function() {
+    var trimmedLine;
+    trimmedLine = Utils.trim(this.currentLine, ' ');
+    return trimmedLine.length === 0 || trimmedLine.charAt(0) === '#';
+  };
+
+  Parser.prototype.isCurrentLineBlank = function() {
+    return '' === Utils.trim(this.currentLine, ' ');
+  };
+
+  Parser.prototype.isCurrentLineComment = function() {
+    var ltrimmedLine;
+    ltrimmedLine = Utils.ltrim(this.currentLine, ' ');
+    return ltrimmedLine.charAt(0) === '#';
+  };
+
+  Parser.prototype.cleanup = function(value) {
+    var count, i, indent, j, l, len, len1, line, lines, ref, ref1, ref2, smallestIndent, trimmedValue;
+    if (value.indexOf("\r") !== -1) {
+      value = value.split("\r\n").join("\n").split("\r").join("\n");
+    }
+    count = 0;
+    ref = this.PATTERN_YAML_HEADER.replaceAll(value, ''), value = ref[0], count = ref[1];
+    this.offset += count;
+    ref1 = this.PATTERN_LEADING_COMMENTS.replaceAll(value, '', 1), trimmedValue = ref1[0], count = ref1[1];
+    if (count === 1) {
+      this.offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n");
+      value = trimmedValue;
+    }
+    ref2 = this.PATTERN_DOCUMENT_MARKER_START.replaceAll(value, '', 1), trimmedValue = ref2[0], count = ref2[1];
+    if (count === 1) {
+      this.offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n");
+      value = trimmedValue;
+      value = this.PATTERN_DOCUMENT_MARKER_END.replace(value, '');
+    }
+    lines = value.split("\n");
+    smallestIndent = -1;
+    for (j = 0, len = lines.length; j < len; j++) {
+      line = lines[j];
+      if (Utils.trim(line, ' ').length === 0) {
+        continue;
+      }
+      indent = line.length - Utils.ltrim(line).length;
+      if (smallestIndent === -1 || indent < smallestIndent) {
+        smallestIndent = indent;
+      }
+    }
+    if (smallestIndent > 0) {
+      for (i = l = 0, len1 = lines.length; l < len1; i = ++l) {
+        line = lines[i];
+        lines[i] = line.slice(smallestIndent);
+      }
+      value = lines.join("\n");
+    }
+    return value;
+  };
+
+  Parser.prototype.isNextLineUnIndentedCollection = function(currentIndentation) {
+    var notEOF, ret;
+    if (currentIndentation == null) {
+      currentIndentation = null;
+    }
+    if (currentIndentation == null) {
+      currentIndentation = this.getCurrentLineIndentation();
+    }
+    notEOF = this.moveToNextLine();
+    while (notEOF && this.isCurrentLineEmpty()) {
+      notEOF = this.moveToNextLine();
+    }
+    if (false === notEOF) {
+      return false;
+    }
+    ret = false;
+    if (this.getCurrentLineIndentation() === currentIndentation && this.isStringUnIndentedCollectionItem(this.currentLine)) {
+      ret = true;
+    }
+    this.moveToPreviousLine();
+    return ret;
+  };
+
+  Parser.prototype.isStringUnIndentedCollectionItem = function() {
+    return this.currentLine === '-' || this.currentLine.slice(0, 2) === '- ';
+  };
+
+  return Parser;
+
+})();
+
+module.exports = Parser;
diff --git a/src/main/webapp/scripts/lib/yamljs/lib/Pattern.js b/src/main/webapp/scripts/lib/yamljs/lib/Pattern.js
new file mode 100755
index 0000000000000000000000000000000000000000..15a8ce9854c6381737c7f7c40937c8051567985b
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/lib/Pattern.js
@@ -0,0 +1,119 @@
+// Generated by CoffeeScript 1.10.0
+var Pattern;
+
+Pattern = (function() {
+  Pattern.prototype.regex = null;
+
+  Pattern.prototype.rawRegex = null;
+
+  Pattern.prototype.cleanedRegex = null;
+
+  Pattern.prototype.mapping = null;
+
+  function Pattern(rawRegex, modifiers) {
+    var _char, capturingBracketNumber, cleanedRegex, i, len, mapping, name, part, subChar;
+    if (modifiers == null) {
+      modifiers = '';
+    }
+    cleanedRegex = '';
+    len = rawRegex.length;
+    mapping = null;
+    capturingBracketNumber = 0;
+    i = 0;
+    while (i < len) {
+      _char = rawRegex.charAt(i);
+      if (_char === '\\') {
+        cleanedRegex += rawRegex.slice(i, +(i + 1) + 1 || 9e9);
+        i++;
+      } else if (_char === '(') {
+        if (i < len - 2) {
+          part = rawRegex.slice(i, +(i + 2) + 1 || 9e9);
+          if (part === '(?:') {
+            i += 2;
+            cleanedRegex += part;
+          } else if (part === '(?<') {
+            capturingBracketNumber++;
+            i += 2;
+            name = '';
+            while (i + 1 < len) {
+              subChar = rawRegex.charAt(i + 1);
+              if (subChar === '>') {
+                cleanedRegex += '(';
+                i++;
+                if (name.length > 0) {
+                  if (mapping == null) {
+                    mapping = {};
+                  }
+                  mapping[name] = capturingBracketNumber;
+                }
+                break;
+              } else {
+                name += subChar;
+              }
+              i++;
+            }
+          } else {
+            cleanedRegex += _char;
+            capturingBracketNumber++;
+          }
+        } else {
+          cleanedRegex += _char;
+        }
+      } else {
+        cleanedRegex += _char;
+      }
+      i++;
+    }
+    this.rawRegex = rawRegex;
+    this.cleanedRegex = cleanedRegex;
+    this.regex = new RegExp(this.cleanedRegex, 'g' + modifiers.replace('g', ''));
+    this.mapping = mapping;
+  }
+
+  Pattern.prototype.exec = function(str) {
+    var index, matches, name, ref;
+    this.regex.lastIndex = 0;
+    matches = this.regex.exec(str);
+    if (matches == null) {
+      return null;
+    }
+    if (this.mapping != null) {
+      ref = this.mapping;
+      for (name in ref) {
+        index = ref[name];
+        matches[name] = matches[index];
+      }
+    }
+    return matches;
+  };
+
+  Pattern.prototype.test = function(str) {
+    this.regex.lastIndex = 0;
+    return this.regex.test(str);
+  };
+
+  Pattern.prototype.replace = function(str, replacement) {
+    this.regex.lastIndex = 0;
+    return str.replace(this.regex, replacement);
+  };
+
+  Pattern.prototype.replaceAll = function(str, replacement, limit) {
+    var count;
+    if (limit == null) {
+      limit = 0;
+    }
+    this.regex.lastIndex = 0;
+    count = 0;
+    while (this.regex.test(str) && (limit === 0 || count < limit)) {
+      this.regex.lastIndex = 0;
+      str = str.replace(this.regex, '');
+      count++;
+    }
+    return [str, count];
+  };
+
+  return Pattern;
+
+})();
+
+module.exports = Pattern;
diff --git a/src/main/webapp/scripts/lib/yamljs/lib/Unescaper.js b/src/main/webapp/scripts/lib/yamljs/lib/Unescaper.js
new file mode 100755
index 0000000000000000000000000000000000000000..aeb54dd423780a45a8ff64aef8a61e076ec8f250
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/lib/Unescaper.js
@@ -0,0 +1,83 @@
+// Generated by CoffeeScript 1.10.0
+var Pattern, Unescaper, Utils;
+
+Utils = require('./Utils');
+
+Pattern = require('./Pattern');
+
+Unescaper = (function() {
+  function Unescaper() {}
+
+  Unescaper.PATTERN_ESCAPED_CHARACTER = new Pattern('\\\\([0abt\tnvfre "\\/\\\\N_LP]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})');
+
+  Unescaper.unescapeSingleQuotedString = function(value) {
+    return value.replace(/\'\'/g, '\'');
+  };
+
+  Unescaper.unescapeDoubleQuotedString = function(value) {
+    if (this._unescapeCallback == null) {
+      this._unescapeCallback = (function(_this) {
+        return function(str) {
+          return _this.unescapeCharacter(str);
+        };
+      })(this);
+    }
+    return this.PATTERN_ESCAPED_CHARACTER.replace(value, this._unescapeCallback);
+  };
+
+  Unescaper.unescapeCharacter = function(value) {
+    var ch;
+    ch = String.fromCharCode;
+    switch (value.charAt(1)) {
+      case '0':
+        return ch(0);
+      case 'a':
+        return ch(7);
+      case 'b':
+        return ch(8);
+      case 't':
+        return "\t";
+      case "\t":
+        return "\t";
+      case 'n':
+        return "\n";
+      case 'v':
+        return ch(11);
+      case 'f':
+        return ch(12);
+      case 'r':
+        return ch(13);
+      case 'e':
+        return ch(27);
+      case ' ':
+        return ' ';
+      case '"':
+        return '"';
+      case '/':
+        return '/';
+      case '\\':
+        return '\\';
+      case 'N':
+        return ch(0x0085);
+      case '_':
+        return ch(0x00A0);
+      case 'L':
+        return ch(0x2028);
+      case 'P':
+        return ch(0x2029);
+      case 'x':
+        return Utils.utf8chr(Utils.hexDec(value.substr(2, 2)));
+      case 'u':
+        return Utils.utf8chr(Utils.hexDec(value.substr(2, 4)));
+      case 'U':
+        return Utils.utf8chr(Utils.hexDec(value.substr(2, 8)));
+      default:
+        return '';
+    }
+  };
+
+  return Unescaper;
+
+})();
+
+module.exports = Unescaper;
diff --git a/src/main/webapp/scripts/lib/yamljs/lib/Utils.js b/src/main/webapp/scripts/lib/yamljs/lib/Utils.js
new file mode 100755
index 0000000000000000000000000000000000000000..1a360c2f36f1599ed602ad40256a04fac5818ee2
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/lib/Utils.js
@@ -0,0 +1,284 @@
+// Generated by CoffeeScript 1.10.0
+var Pattern, Utils;
+
+Pattern = require('./Pattern');
+
+Utils = (function() {
+  function Utils() {}
+
+  Utils.REGEX_LEFT_TRIM_BY_CHAR = {};
+
+  Utils.REGEX_RIGHT_TRIM_BY_CHAR = {};
+
+  Utils.REGEX_SPACES = /\s+/g;
+
+  Utils.REGEX_DIGITS = /^\d+$/;
+
+  Utils.REGEX_OCTAL = /[^0-7]/gi;
+
+  Utils.REGEX_HEXADECIMAL = /[^a-f0-9]/gi;
+
+  Utils.PATTERN_DATE = new Pattern('^' + '(?<year>[0-9][0-9][0-9][0-9])' + '-(?<month>[0-9][0-9]?)' + '-(?<day>[0-9][0-9]?)' + '(?:(?:[Tt]|[ \t]+)' + '(?<hour>[0-9][0-9]?)' + ':(?<minute>[0-9][0-9])' + ':(?<second>[0-9][0-9])' + '(?:\.(?<fraction>[0-9]*))?' + '(?:[ \t]*(?<tz>Z|(?<tz_sign>[-+])(?<tz_hour>[0-9][0-9]?)' + '(?::(?<tz_minute>[0-9][0-9]))?))?)?' + '$', 'i');
+
+  Utils.LOCAL_TIMEZONE_OFFSET = new Date().getTimezoneOffset() * 60 * 1000;
+
+  Utils.trim = function(str, _char) {
+    var regexLeft, regexRight;
+    if (_char == null) {
+      _char = '\\s';
+    }
+    return str.trim();
+    regexLeft = this.REGEX_LEFT_TRIM_BY_CHAR[_char];
+    if (regexLeft == null) {
+      this.REGEX_LEFT_TRIM_BY_CHAR[_char] = regexLeft = new RegExp('^' + _char + '' + _char + '*');
+    }
+    regexLeft.lastIndex = 0;
+    regexRight = this.REGEX_RIGHT_TRIM_BY_CHAR[_char];
+    if (regexRight == null) {
+      this.REGEX_RIGHT_TRIM_BY_CHAR[_char] = regexRight = new RegExp(_char + '' + _char + '*$');
+    }
+    regexRight.lastIndex = 0;
+    return str.replace(regexLeft, '').replace(regexRight, '');
+  };
+
+  Utils.ltrim = function(str, _char) {
+    var regexLeft;
+    if (_char == null) {
+      _char = '\\s';
+    }
+    regexLeft = this.REGEX_LEFT_TRIM_BY_CHAR[_char];
+    if (regexLeft == null) {
+      this.REGEX_LEFT_TRIM_BY_CHAR[_char] = regexLeft = new RegExp('^' + _char + '' + _char + '*');
+    }
+    regexLeft.lastIndex = 0;
+    return str.replace(regexLeft, '');
+  };
+
+  Utils.rtrim = function(str, _char) {
+    var regexRight;
+    if (_char == null) {
+      _char = '\\s';
+    }
+    regexRight = this.REGEX_RIGHT_TRIM_BY_CHAR[_char];
+    if (regexRight == null) {
+      this.REGEX_RIGHT_TRIM_BY_CHAR[_char] = regexRight = new RegExp(_char + '' + _char + '*$');
+    }
+    regexRight.lastIndex = 0;
+    return str.replace(regexRight, '');
+  };
+
+  Utils.isEmpty = function(value) {
+    return !value || value === '' || value === '0' || (value instanceof Array && value.length === 0);
+  };
+
+  Utils.subStrCount = function(string, subString, start, length) {
+    var c, i, j, len, ref, sublen;
+    c = 0;
+    string = '' + string;
+    subString = '' + subString;
+    if (start != null) {
+      string = string.slice(start);
+    }
+    if (length != null) {
+      string = string.slice(0, length);
+    }
+    len = string.length;
+    sublen = subString.length;
+    for (i = j = 0, ref = len; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
+      if (subString === string.slice(i, sublen)) {
+        c++;
+        i += sublen - 1;
+      }
+    }
+    return c;
+  };
+
+  Utils.isDigits = function(input) {
+    this.REGEX_DIGITS.lastIndex = 0;
+    return this.REGEX_DIGITS.test(input);
+  };
+
+  Utils.octDec = function(input) {
+    this.REGEX_OCTAL.lastIndex = 0;
+    return parseInt((input + '').replace(this.REGEX_OCTAL, ''), 8);
+  };
+
+  Utils.hexDec = function(input) {
+    this.REGEX_HEXADECIMAL.lastIndex = 0;
+    input = this.trim(input);
+    if ((input + '').slice(0, 2) === '0x') {
+      input = (input + '').slice(2);
+    }
+    return parseInt((input + '').replace(this.REGEX_HEXADECIMAL, ''), 16);
+  };
+
+  Utils.utf8chr = function(c) {
+    var ch;
+    ch = String.fromCharCode;
+    if (0x80 > (c %= 0x200000)) {
+      return ch(c);
+    }
+    if (0x800 > c) {
+      return ch(0xC0 | c >> 6) + ch(0x80 | c & 0x3F);
+    }
+    if (0x10000 > c) {
+      return ch(0xE0 | c >> 12) + ch(0x80 | c >> 6 & 0x3F) + ch(0x80 | c & 0x3F);
+    }
+    return ch(0xF0 | c >> 18) + ch(0x80 | c >> 12 & 0x3F) + ch(0x80 | c >> 6 & 0x3F) + ch(0x80 | c & 0x3F);
+  };
+
+  Utils.parseBoolean = function(input, strict) {
+    var lowerInput;
+    if (strict == null) {
+      strict = true;
+    }
+    if (typeof input === 'string') {
+      lowerInput = input.toLowerCase();
+      if (!strict) {
+        if (lowerInput === 'no') {
+          return false;
+        }
+      }
+      if (lowerInput === '0') {
+        return false;
+      }
+      if (lowerInput === 'false') {
+        return false;
+      }
+      if (lowerInput === '') {
+        return false;
+      }
+      return true;
+    }
+    return !!input;
+  };
+
+  Utils.isNumeric = function(input) {
+    this.REGEX_SPACES.lastIndex = 0;
+    return typeof input === 'number' || typeof input === 'string' && !isNaN(input) && input.replace(this.REGEX_SPACES, '') !== '';
+  };
+
+  Utils.stringToDate = function(str) {
+    var date, day, fraction, hour, info, minute, month, second, tz_hour, tz_minute, tz_offset, year;
+    if (!(str != null ? str.length : void 0)) {
+      return null;
+    }
+    info = this.PATTERN_DATE.exec(str);
+    if (!info) {
+      return null;
+    }
+    year = parseInt(info.year, 10);
+    month = parseInt(info.month, 10) - 1;
+    day = parseInt(info.day, 10);
+    if (info.hour == null) {
+      date = new Date(Date.UTC(year, month, day));
+      return date;
+    }
+    hour = parseInt(info.hour, 10);
+    minute = parseInt(info.minute, 10);
+    second = parseInt(info.second, 10);
+    if (info.fraction != null) {
+      fraction = info.fraction.slice(0, 3);
+      while (fraction.length < 3) {
+        fraction += '0';
+      }
+      fraction = parseInt(fraction, 10);
+    } else {
+      fraction = 0;
+    }
+    if (info.tz != null) {
+      tz_hour = parseInt(info.tz_hour, 10);
+      if (info.tz_minute != null) {
+        tz_minute = parseInt(info.tz_minute, 10);
+      } else {
+        tz_minute = 0;
+      }
+      tz_offset = (tz_hour * 60 + tz_minute) * 60000;
+      if ('-' === info.tz_sign) {
+        tz_offset *= -1;
+      }
+    }
+    date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction));
+    if (tz_offset) {
+      date.setTime(date.getTime() + tz_offset);
+    }
+    return date;
+  };
+
+  Utils.strRepeat = function(str, number) {
+    var i, res;
+    res = '';
+    i = 0;
+    while (i < number) {
+      res += str;
+      i++;
+    }
+    return res;
+  };
+
+  Utils.getStringFromFile = function(path, callback) {
+    var data, fs, j, len1, name, ref, req, xhr;
+    if (callback == null) {
+      callback = null;
+    }
+    xhr = null;
+    if (typeof window !== "undefined" && window !== null) {
+      if (window.XMLHttpRequest) {
+        xhr = new XMLHttpRequest();
+      } else if (window.ActiveXObject) {
+        ref = ["Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.3.0", "Msxml2.XMLHTTP", "Microsoft.XMLHTTP"];
+        for (j = 0, len1 = ref.length; j < len1; j++) {
+          name = ref[j];
+          try {
+            xhr = new ActiveXObject(name);
+          } catch (undefined) {}
+        }
+      }
+    }
+    if (xhr != null) {
+      if (callback != null) {
+        xhr.onreadystatechange = function() {
+          if (xhr.readyState === 4) {
+            if (xhr.status === 200 || xhr.status === 0) {
+              return callback(xhr.responseText);
+            } else {
+              return callback(null);
+            }
+          }
+        };
+        xhr.open('GET', path, true);
+        return xhr.send(null);
+      } else {
+        xhr.open('GET', path, false);
+        xhr.send(null);
+        if (xhr.status === 200 || xhr.status === 0) {
+          return xhr.responseText;
+        }
+        return null;
+      }
+    } else {
+      req = require;
+      fs = req('fs');
+      if (callback != null) {
+        return fs.readFile(path, function(err, data) {
+          if (err) {
+            return callback(null);
+          } else {
+            return callback(String(data));
+          }
+        });
+      } else {
+        data = fs.readFileSync(path);
+        if (data != null) {
+          return String(data);
+        }
+        return null;
+      }
+    }
+  };
+
+  return Utils;
+
+})();
+
+module.exports = Utils;
diff --git a/src/main/webapp/scripts/lib/yamljs/lib/Yaml.js b/src/main/webapp/scripts/lib/yamljs/lib/Yaml.js
new file mode 100755
index 0000000000000000000000000000000000000000..8f93694bf1d1bc8d1238d533b4fdf5fe6c40d724
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/lib/Yaml.js
@@ -0,0 +1,104 @@
+// Generated by CoffeeScript 1.10.0
+var Dumper, Parser, Utils, Yaml;
+
+Parser = require('./Parser');
+
+Dumper = require('./Dumper');
+
+Utils = require('./Utils');
+
+Yaml = (function() {
+  function Yaml() {}
+
+  Yaml.parse = function(input, exceptionOnInvalidType, objectDecoder) {
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectDecoder == null) {
+      objectDecoder = null;
+    }
+    return new Parser().parse(input, exceptionOnInvalidType, objectDecoder);
+  };
+
+  Yaml.parseFile = function(path, callback, exceptionOnInvalidType, objectDecoder) {
+    var input;
+    if (callback == null) {
+      callback = null;
+    }
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectDecoder == null) {
+      objectDecoder = null;
+    }
+    if (callback != null) {
+      return Utils.getStringFromFile(path, (function(_this) {
+        return function(input) {
+          var result;
+          result = null;
+          if (input != null) {
+            result = _this.parse(input, exceptionOnInvalidType, objectDecoder);
+          }
+          callback(result);
+        };
+      })(this));
+    } else {
+      input = Utils.getStringFromFile(path);
+      if (input != null) {
+        return this.parse(input, exceptionOnInvalidType, objectDecoder);
+      }
+      return null;
+    }
+  };
+
+  Yaml.dump = function(input, inline, indent, exceptionOnInvalidType, objectEncoder) {
+    var yaml;
+    if (inline == null) {
+      inline = 2;
+    }
+    if (indent == null) {
+      indent = 4;
+    }
+    if (exceptionOnInvalidType == null) {
+      exceptionOnInvalidType = false;
+    }
+    if (objectEncoder == null) {
+      objectEncoder = null;
+    }
+    yaml = new Dumper();
+    yaml.indentation = indent;
+    return yaml.dump(input, inline, 0, exceptionOnInvalidType, objectEncoder);
+  };
+
+  Yaml.register = function() {
+    var require_handler;
+    require_handler = function(module, filename) {
+      return module.exports = YAML.parseFile(filename);
+    };
+    if ((typeof require !== "undefined" && require !== null ? require.extensions : void 0) != null) {
+      require.extensions['.yml'] = require_handler;
+      return require.extensions['.yaml'] = require_handler;
+    }
+  };
+
+  Yaml.stringify = function(input, inline, indent, exceptionOnInvalidType, objectEncoder) {
+    return this.dump(input, inline, indent, exceptionOnInvalidType, objectEncoder);
+  };
+
+  Yaml.load = function(path, callback, exceptionOnInvalidType, objectDecoder) {
+    return this.parseFile(path, callback, exceptionOnInvalidType, objectDecoder);
+  };
+
+  return Yaml;
+
+})();
+
+if (typeof window !== "undefined" && window !== null) {
+  window.YAML = Yaml;
+}
+
+if (typeof window === "undefined" || window === null) {
+  this.YAML = Yaml;
+}
+
+module.exports = Yaml;
diff --git a/src/main/webapp/scripts/lib/yamljs/package.json b/src/main/webapp/scripts/lib/yamljs/package.json
new file mode 100755
index 0000000000000000000000000000000000000000..e17e2b57996646c6d8833b9b7ca091e684b8fc7d
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/package.json
@@ -0,0 +1,34 @@
+{
+  "name": "yamljs",
+  "version": "0.2.7",
+  "description": "Standalone JavaScript YAML 1.2 Parser & Encoder. Works under node.js and all major browsers. Also brings command line YAML/JSON conversion tools.",
+  "keywords": [
+    "yaml",
+    "json",
+    "yaml2json",
+    "json2yaml"
+  ],
+  "author": "Jeremy Faivre <contact@jeremyfa.com>",
+  "main": "./lib/Yaml.js",
+  "dependencies": {
+    "argparse": "^0.1.15",
+    "glob": "^4.0.0"
+  },
+  "devDependencies": {
+    "coffeeify": "^0.7.0",
+    "benchmark": "^1.0.0",
+    "jasmine-node": "^1.14.5"
+  },
+  "bin": {
+    "yaml2json": "./bin/yaml2json",
+    "json2yaml": "./bin/json2yaml"
+  },
+  "scripts": {
+    "test": "cake build; cake test"
+  },
+  "license": "MIT",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/jeremyfa/yaml.js.git"
+  }
+}
diff --git a/src/main/webapp/scripts/lib/yamljs/src/Dumper.coffee b/src/main/webapp/scripts/lib/yamljs/src/Dumper.coffee
new file mode 100755
index 0000000000000000000000000000000000000000..03382121bb7a52af746e99387e330f0e2e113ead
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/src/Dumper.coffee
@@ -0,0 +1,56 @@
+
+Utils   = require './Utils'
+Inline  = require './Inline'
+
+# Dumper dumps JavaScript variables to YAML strings.
+#
+class Dumper
+
+    # The amount of spaces to use for indentation of nested nodes.
+    @indentation:   4
+
+
+    # Dumps a JavaScript value to YAML.
+    #
+    # @param [Object]   input                   The JavaScript value
+    # @param [Integer]  inline                  The level where you switch to inline YAML
+    # @param [Integer]  indent                  The level of indentation (used internally)
+    # @param [Boolean]  exceptionOnInvalidType  true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise
+    # @param [Function] objectEncoder           A function to serialize custom objects, null otherwise
+    #
+    # @return [String]  The YAML representation of the JavaScript value
+    #
+    dump: (input, inline = 0, indent = 0, exceptionOnInvalidType = false, objectEncoder = null) ->
+        output = ''
+        prefix = (if indent then Utils.strRepeat(' ', indent) else '')
+
+        if inline <= 0 or typeof(input) isnt 'object' or input instanceof Date or Utils.isEmpty(input)
+            output += prefix + Inline.dump(input, exceptionOnInvalidType, objectEncoder)
+        
+        else
+            if input instanceof Array
+                for value in input
+                    willBeInlined = (inline - 1 <= 0 or typeof(value) isnt 'object' or Utils.isEmpty(value))
+
+                    output +=
+                        prefix +
+                        '-' +
+                        (if willBeInlined then ' ' else "\n") +
+                        @dump(value, inline - 1, (if willBeInlined then 0 else indent + @indentation), exceptionOnInvalidType, objectEncoder) +
+                        (if willBeInlined then "\n" else '')
+
+            else
+                for key, value of input
+                    willBeInlined = (inline - 1 <= 0 or typeof(value) isnt 'object' or Utils.isEmpty(value))
+
+                    output +=
+                        prefix +
+                        Inline.dump(key, exceptionOnInvalidType, objectEncoder) + ':' +
+                        (if willBeInlined then ' ' else "\n") +
+                        @dump(value, inline - 1, (if willBeInlined then 0 else indent + @indentation), exceptionOnInvalidType, objectEncoder) +
+                        (if willBeInlined then "\n" else '')
+
+        return output
+
+
+module.exports = Dumper
diff --git a/src/main/webapp/scripts/lib/yamljs/src/Escaper.coffee b/src/main/webapp/scripts/lib/yamljs/src/Escaper.coffee
new file mode 100755
index 0000000000000000000000000000000000000000..0eefec77dbe330f435da60c49ac30a65a4d6a8dd
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/src/Escaper.coffee
@@ -0,0 +1,80 @@
+
+Pattern = require './Pattern'
+
+# Escaper encapsulates escaping rules for single
+# and double-quoted YAML strings.
+class Escaper
+
+    # Mapping arrays for escaping a double quoted string. The backslash is
+    # first to ensure proper escaping.
+    @LIST_ESCAPEES:                 ['\\', '\\\\', '\\"', '"',
+                                     "\x00",  "\x01",  "\x02",  "\x03",  "\x04",  "\x05",  "\x06",  "\x07",
+                                     "\x08",  "\x09",  "\x0a",  "\x0b",  "\x0c",  "\x0d",  "\x0e",  "\x0f",
+                                     "\x10",  "\x11",  "\x12",  "\x13",  "\x14",  "\x15",  "\x16",  "\x17",
+                                     "\x18",  "\x19",  "\x1a",  "\x1b",  "\x1c",  "\x1d",  "\x1e",  "\x1f",
+                                     (ch = String.fromCharCode)(0x0085), ch(0x00A0), ch(0x2028), ch(0x2029)]
+    @LIST_ESCAPED:                  ['\\\\', '\\"', '\\"', '\\"',
+                                     "\\0",   "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\a",
+                                     "\\b",   "\\t",   "\\n",   "\\v",   "\\f",   "\\r",   "\\x0e", "\\x0f",
+                                     "\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17",
+                                     "\\x18", "\\x19", "\\x1a", "\\e",   "\\x1c", "\\x1d", "\\x1e", "\\x1f",
+                                     "\\N", "\\_", "\\L", "\\P"]
+
+    @MAPPING_ESCAPEES_TO_ESCAPED:   do =>
+        mapping = {}
+        for i in [0...@LIST_ESCAPEES.length]
+            mapping[@LIST_ESCAPEES[i]] = @LIST_ESCAPED[i]
+        return mapping
+
+    # Characters that would cause a dumped string to require double quoting.
+    @PATTERN_CHARACTERS_TO_ESCAPE:  new Pattern '[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9'
+
+    # Other precompiled patterns
+    @PATTERN_MAPPING_ESCAPEES:      new Pattern @LIST_ESCAPEES.join('|').split('\\').join('\\\\')
+    @PATTERN_SINGLE_QUOTING:        new Pattern '[\\s\'":{}[\\],&*#?]|^[-?|<>=!%@`]'
+
+
+
+    # Determines if a JavaScript value would require double quoting in YAML.
+    #
+    # @param [String]   value   A JavaScript value value
+    #
+    # @return [Boolean] true    if the value would require double quotes.
+    #
+    @requiresDoubleQuoting: (value) ->
+        return @PATTERN_CHARACTERS_TO_ESCAPE.test value
+
+
+    # Escapes and surrounds a JavaScript value with double quotes.
+    #
+    # @param [String]   value   A JavaScript value
+    #
+    # @return [String]  The quoted, escaped string
+    #
+    @escapeWithDoubleQuotes: (value) ->
+        result = @PATTERN_MAPPING_ESCAPEES.replace value, (str) =>
+            return @MAPPING_ESCAPEES_TO_ESCAPED[str]
+        return '"'+result+'"'
+
+
+    # Determines if a JavaScript value would require single quoting in YAML.
+    #
+    # @param [String]   value   A JavaScript value
+    #
+    # @return [Boolean] true if the value would require single quotes.
+    #
+    @requiresSingleQuoting: (value) ->
+        return @PATTERN_SINGLE_QUOTING.test value
+
+
+    # Escapes and surrounds a JavaScript value with single quotes.
+    #
+    # @param [String]   value   A JavaScript value
+    #
+    # @return [String]  The quoted, escaped string
+    #
+    @escapeWithSingleQuotes: (value) ->
+        return "'"+value.replace(/'/g, "''")+"'"
+
+
+module.exports = Escaper
diff --git a/src/main/webapp/scripts/lib/yamljs/src/Exception/DumpException.coffee b/src/main/webapp/scripts/lib/yamljs/src/Exception/DumpException.coffee
new file mode 100755
index 0000000000000000000000000000000000000000..9cc6c27ba4c60d6316a04dd5d4dd0d9d46af80ab
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/src/Exception/DumpException.coffee
@@ -0,0 +1,12 @@
+
+class DumpException extends Error
+
+    constructor: (@message, @parsedLine, @snippet) ->
+
+    toString: ->
+        if @parsedLine? and @snippet?
+            return '<DumpException> ' + @message + ' (line ' + @parsedLine + ': \'' + @snippet + '\')'
+        else
+            return '<DumpException> ' + @message
+
+module.exports = DumpException
diff --git a/src/main/webapp/scripts/lib/yamljs/src/Exception/ParseException.coffee b/src/main/webapp/scripts/lib/yamljs/src/Exception/ParseException.coffee
new file mode 100755
index 0000000000000000000000000000000000000000..a6a0785f3dbd9ea9545d5efed1bfd21282d49eda
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/src/Exception/ParseException.coffee
@@ -0,0 +1,12 @@
+
+class ParseException extends Error
+
+    constructor: (@message, @parsedLine, @snippet) ->
+
+    toString: ->
+        if @parsedLine? and @snippet?
+            return '<ParseException> ' + @message + ' (line ' + @parsedLine + ': \'' + @snippet + '\')'
+        else
+            return '<ParseException> ' + @message
+
+module.exports = ParseException
diff --git a/src/main/webapp/scripts/lib/yamljs/src/Inline.coffee b/src/main/webapp/scripts/lib/yamljs/src/Inline.coffee
new file mode 100755
index 0000000000000000000000000000000000000000..a0f84348be5dbf73cb9938163c8d1f59f294e89e
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/src/Inline.coffee
@@ -0,0 +1,487 @@
+
+Pattern         = require './Pattern'
+Unescaper       = require './Unescaper'
+Escaper         = require './Escaper'
+Utils           = require './Utils'
+ParseException  = require './Exception/ParseException'
+DumpException   = require './Exception/DumpException'
+
+# Inline YAML parsing and dumping
+class Inline
+
+    # Quoted string regular expression
+    @REGEX_QUOTED_STRING:               '(?:"(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\']*(?:\'\'[^\']*)*)\')'
+
+    # Pre-compiled patterns
+    #
+    @PATTERN_TRAILING_COMMENTS:         new Pattern '^\\s*#.*$'
+    @PATTERN_QUOTED_SCALAR:             new Pattern '^'+@REGEX_QUOTED_STRING
+    @PATTERN_THOUSAND_NUMERIC_SCALAR:   new Pattern '^(-|\\+)?[0-9,]+(\\.[0-9]+)?$'
+    @PATTERN_SCALAR_BY_DELIMITERS:      {}
+
+    # Settings
+    @settings: {}
+
+
+    # Configure YAML inline.
+    #
+    # @param [Boolean]  exceptionOnInvalidType  true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise
+    # @param [Function] objectDecoder           A function to deserialize custom objects, null otherwise
+    #
+    @configure: (exceptionOnInvalidType = null, objectDecoder = null) ->
+        # Update settings
+        @settings.exceptionOnInvalidType = exceptionOnInvalidType
+        @settings.objectDecoder = objectDecoder
+        return
+
+
+    # Converts a YAML string to a JavaScript object.
+    #
+    # @param [String]   value                   A YAML string
+    # @param [Boolean]  exceptionOnInvalidType  true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise
+    # @param [Function] objectDecoder           A function to deserialize custom objects, null otherwise
+    #
+    # @return [Object]  A JavaScript object representing the YAML string
+    #
+    # @throw [ParseException]
+    #
+    @parse: (value, exceptionOnInvalidType = false, objectDecoder = null) ->
+        # Update settings from last call of Inline.parse()
+        @settings.exceptionOnInvalidType = exceptionOnInvalidType
+        @settings.objectDecoder = objectDecoder
+
+        if not value?
+            return ''
+
+        value = Utils.trim value
+
+        if 0 is value.length
+            return ''
+
+        # Keep a context object to pass through static methods
+        context = {exceptionOnInvalidType, objectDecoder, i: 0}
+
+        switch value.charAt(0)
+            when '['
+                result = @parseSequence value, context
+                ++context.i
+            when '{'
+                result = @parseMapping value, context
+                ++context.i
+            else
+                result = @parseScalar value, null, ['"', "'"], context
+
+        # Some comments are allowed at the end
+        if @PATTERN_TRAILING_COMMENTS.replace(value[context.i..], '') isnt ''
+            throw new ParseException 'Unexpected characters near "'+value[context.i..]+'".'
+
+        return result
+
+
+    # Dumps a given JavaScript variable to a YAML string.
+    #
+    # @param [Object]   value                   The JavaScript variable to convert
+    # @param [Boolean]  exceptionOnInvalidType  true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise
+    # @param [Function] objectEncoder           A function to serialize custom objects, null otherwise
+    #
+    # @return [String]  The YAML string representing the JavaScript object
+    #
+    # @throw [DumpException]
+    #
+    @dump: (value, exceptionOnInvalidType = false, objectEncoder = null) ->
+        if not value?
+            return 'null'
+        type = typeof value
+        if type is 'object'
+            if value instanceof Date
+                return value.toISOString()
+            else if objectEncoder?
+                result = objectEncoder value
+                if typeof result is 'string' or result?
+                    return result
+            return @dumpObject value
+        if type is 'boolean'
+            return (if value then 'true' else 'false')
+        if Utils.isDigits(value)
+            return (if type is 'string' then "'"+value+"'" else String(parseInt(value)))
+        if Utils.isNumeric(value)
+            return (if type is 'string' then "'"+value+"'" else String(parseFloat(value)))
+        if type is 'number'
+            return (if value is Infinity then '.Inf' else (if value is -Infinity then '-.Inf' else (if isNaN(value) then '.NaN' else value)))
+        if Escaper.requiresDoubleQuoting value
+            return Escaper.escapeWithDoubleQuotes value
+        if Escaper.requiresSingleQuoting value
+            return Escaper.escapeWithSingleQuotes value
+        if '' is value
+            return '""'
+        if Utils.PATTERN_DATE.test value
+            return "'"+value+"'";
+        if value.toLowerCase() in ['null','~','true','false']
+            return "'"+value+"'"
+        # Default
+        return value;
+
+
+    # Dumps a JavaScript object to a YAML string.
+    #
+    # @param [Object]   value                   The JavaScript object to dump
+    # @param [Boolean]  exceptionOnInvalidType  true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise
+    # @param [Function] objectEncoder           A function do serialize custom objects, null otherwise
+    #
+    # @return string The YAML string representing the JavaScript object
+    #
+    @dumpObject: (value, exceptionOnInvalidType, objectSupport = null) ->
+        # Array
+        if value instanceof Array
+            output = []
+            for val in value
+                output.push @dump val
+            return '['+output.join(', ')+']'
+
+        # Mapping
+        else
+            output = []
+            for key, val of value
+                output.push @dump(key)+': '+@dump(val)
+            return '{'+output.join(', ')+'}'
+
+
+    # Parses a scalar to a YAML string.
+    #
+    # @param [Object]   scalar
+    # @param [Array]    delimiters
+    # @param [Array]    stringDelimiters
+    # @param [Object]   context
+    # @param [Boolean]  evaluate
+    #
+    # @return [String]  A YAML string
+    #
+    # @throw [ParseException] When malformed inline YAML string is parsed
+    #
+    @parseScalar: (scalar, delimiters = null, stringDelimiters = ['"', "'"], context = null, evaluate = true) ->
+        unless context?
+            context = exceptionOnInvalidType: @settings.exceptionOnInvalidType, objectDecoder: @settings.objectDecoder, i: 0
+        {i} = context
+
+        if scalar.charAt(i) in stringDelimiters
+            # Quoted scalar
+            output = @parseQuotedScalar scalar, context
+            {i} = context
+
+            if delimiters?
+                tmp = Utils.ltrim scalar[i..], ' '
+                if not(tmp.charAt(0) in delimiters)
+                    throw new ParseException 'Unexpected characters ('+scalar[i..]+').'
+
+        else
+            # "normal" string
+            if not delimiters
+                output = scalar[i..]
+                i += output.length
+
+                # Remove comments
+                strpos = output.indexOf ' #'
+                if strpos isnt -1
+                    output = Utils.rtrim output[0...strpos]
+
+            else
+                joinedDelimiters = delimiters.join('|')
+                pattern = @PATTERN_SCALAR_BY_DELIMITERS[joinedDelimiters]
+                unless pattern?
+                    pattern = new Pattern '^(.+?)('+joinedDelimiters+')'
+                    @PATTERN_SCALAR_BY_DELIMITERS[joinedDelimiters] = pattern
+                if match = pattern.exec scalar[i..]
+                    output = match[1]
+                    i += output.length
+                else
+                    throw new ParseException 'Malformed inline YAML string ('+scalar+').'
+
+
+            if evaluate
+                output = @evaluateScalar output, context
+
+        context.i = i
+        return output
+
+
+    # Parses a quoted scalar to YAML.
+    #
+    # @param [String]   scalar
+    # @param [Object]   context
+    #
+    # @return [String]  A YAML string
+    #
+    # @throw [ParseException] When malformed inline YAML string is parsed
+    #
+    @parseQuotedScalar: (scalar, context) ->
+        {i} = context
+
+        unless match = @PATTERN_QUOTED_SCALAR.exec scalar[i..]
+            throw new ParseException 'Malformed inline YAML string ('+scalar[i..]+').'
+
+        output = match[0].substr(1, match[0].length - 2)
+
+        if '"' is scalar.charAt(i)
+            output = Unescaper.unescapeDoubleQuotedString output
+        else
+            output = Unescaper.unescapeSingleQuotedString output
+
+        i += match[0].length
+
+        context.i = i
+        return output
+
+
+    # Parses a sequence to a YAML string.
+    #
+    # @param [String]   sequence
+    # @param [Object]   context
+    #
+    # @return [String]  A YAML string
+    #
+    # @throw [ParseException] When malformed inline YAML string is parsed
+    #
+    @parseSequence: (sequence, context) ->
+        output = []
+        len = sequence.length
+        {i} = context
+        i += 1
+
+        # [foo, bar, ...]
+        while i < len
+            context.i = i
+            switch sequence.charAt(i)
+                when '['
+                    # Nested sequence
+                    output.push @parseSequence sequence, context
+                    {i} = context
+                when '{'
+                    # Nested mapping
+                    output.push @parseMapping sequence, context
+                    {i} = context
+                when ']'
+                    return output
+                when ',', ' ', "\n"
+                    # Do nothing
+                else
+                    isQuoted = (sequence.charAt(i) in ['"', "'"])
+                    value = @parseScalar sequence, [',', ']'], ['"', "'"], context
+                    {i} = context
+
+                    if not(isQuoted) and typeof(value) is 'string' and (value.indexOf(': ') isnt -1 or value.indexOf(":\n") isnt -1)
+                        # Embedded mapping?
+                        try
+                            value = @parseMapping '{'+value+'}'
+                        catch e
+                            # No, it's not
+
+
+                    output.push value
+
+                    --i
+
+            ++i
+
+        throw new ParseException 'Malformed inline YAML string '+sequence
+
+
+    # Parses a mapping to a YAML string.
+    #
+    # @param [String]   mapping
+    # @param [Object]   context
+    #
+    # @return [String]  A YAML string
+    #
+    # @throw [ParseException] When malformed inline YAML string is parsed
+    #
+    @parseMapping: (mapping, context) ->
+        output = {}
+        len = mapping.length
+        {i} = context
+        i += 1
+
+        # {foo: bar, bar:foo, ...}
+        shouldContinueWhileLoop = false
+        while i < len
+            context.i = i
+            switch mapping.charAt(i)
+                when ' ', ',', "\n"
+                    ++i
+                    context.i = i
+                    shouldContinueWhileLoop = true
+                when '}'
+                    return output
+
+            if shouldContinueWhileLoop
+                shouldContinueWhileLoop = false
+                continue
+
+            # Key
+            key = @parseScalar mapping, [':', ' ', "\n"], ['"', "'"], context, false
+            {i} = context
+
+            # Value
+            done = false
+
+            while i < len
+                context.i = i
+                switch mapping.charAt(i)
+                    when '['
+                        # Nested sequence
+                        value = @parseSequence mapping, context
+                        {i} = context
+                        # Spec: Keys MUST be unique; first one wins.
+                        # Parser cannot abort this mapping earlier, since lines
+                        # are processed sequentially.
+                        if output[key] == undefined
+                            output[key] = value
+                        done = true
+                    when '{'
+                        # Nested mapping
+                        value = @parseMapping mapping, context
+                        {i} = context
+                        # Spec: Keys MUST be unique; first one wins.
+                        # Parser cannot abort this mapping earlier, since lines
+                        # are processed sequentially.
+                        if output[key] == undefined
+                            output[key] = value
+                        done = true
+                    when ':', ' ', "\n"
+                        # Do nothing
+                    else
+                        value = @parseScalar mapping, [',', '}'], ['"', "'"], context
+                        {i} = context
+                        # Spec: Keys MUST be unique; first one wins.
+                        # Parser cannot abort this mapping earlier, since lines
+                        # are processed sequentially.
+                        if output[key] == undefined
+                            output[key] = value
+                        done = true
+                        --i
+
+                ++i
+
+                if done
+                    break
+
+        throw new ParseException 'Malformed inline YAML string '+mapping
+
+
+    # Evaluates scalars and replaces magic values.
+    #
+    # @param [String]   scalar
+    #
+    # @return [String]  A YAML string
+    #
+    @evaluateScalar: (scalar, context) ->
+        scalar = Utils.trim(scalar)
+        scalarLower = scalar.toLowerCase()
+
+        switch scalarLower
+            when 'null', '', '~'
+                return null
+            when 'true'
+                return true
+            when 'false'
+                return false
+            when '.inf'
+                return Infinity
+            when '.nan'
+                return NaN
+            when '-.inf'
+                return Infinity
+            else
+                firstChar = scalarLower.charAt(0)
+                switch firstChar
+                    when '!'
+                        firstSpace = scalar.indexOf(' ')
+                        if firstSpace is -1
+                            firstWord = scalarLower
+                        else
+                            firstWord = scalarLower[0...firstSpace]
+                        switch firstWord
+                            when '!'
+                                if firstSpace isnt -1
+                                    return parseInt @parseScalar(scalar[2..])
+                                return null
+                            when '!str'
+                                return Utils.ltrim scalar[4..]
+                            when '!!str'
+                                return Utils.ltrim scalar[5..]
+                            when '!!int'
+                                return parseInt(@parseScalar(scalar[5..]))
+                            when '!!bool'
+                                return Utils.parseBoolean(@parseScalar(scalar[6..]), false)
+                            when '!!float'
+                                return parseFloat(@parseScalar(scalar[7..]))
+                            when '!!timestamp'
+                                return Utils.stringToDate(Utils.ltrim(scalar[11..]))
+                            else
+                                unless context?
+                                    context = exceptionOnInvalidType: @settings.exceptionOnInvalidType, objectDecoder: @settings.objectDecoder, i: 0
+                                {objectDecoder, exceptionOnInvalidType} = context
+
+                                if objectDecoder
+                                    # If objectDecoder function is given, we can do custom decoding of custom types
+                                    trimmedScalar = Utils.rtrim scalar
+                                    firstSpace = trimmedScalar.indexOf(' ')
+                                    if firstSpace is -1
+                                        return objectDecoder trimmedScalar, null
+                                    else
+                                        subValue = Utils.ltrim trimmedScalar[firstSpace+1..]
+                                        unless subValue.length > 0
+                                            subValue = null
+                                        return objectDecoder trimmedScalar[0...firstSpace], subValue
+
+                                if exceptionOnInvalidType
+                                    throw new ParseException 'Custom object support when parsing a YAML file has been disabled.'
+
+                                return null
+                    when '0'
+                        if '0x' is scalar[0...2]
+                            return Utils.hexDec scalar
+                        else if Utils.isDigits scalar
+                            return Utils.octDec scalar
+                        else if Utils.isNumeric scalar
+                            return parseFloat scalar
+                        else
+                            return scalar
+                    when '+'
+                        if Utils.isDigits scalar
+                            raw = scalar
+                            cast = parseInt(raw)
+                            if raw is String(cast)
+                                return cast
+                            else
+                                return raw
+                        else if Utils.isNumeric scalar
+                            return parseFloat scalar
+                        else if @PATTERN_THOUSAND_NUMERIC_SCALAR.test scalar
+                            return parseFloat(scalar.replace(',', ''))
+                        return scalar
+                    when '-'
+                        if Utils.isDigits(scalar[1..])
+                            if '0' is scalar.charAt(1)
+                                return -Utils.octDec(scalar[1..])
+                            else
+                                raw = scalar[1..]
+                                cast = parseInt(raw)
+                                if raw is String(cast)
+                                    return -cast
+                                else
+                                    return -raw
+                        else if Utils.isNumeric scalar
+                            return parseFloat scalar
+                        else if @PATTERN_THOUSAND_NUMERIC_SCALAR.test scalar
+                            return parseFloat(scalar.replace(',', ''))
+                        return scalar
+                    else
+                        if date = Utils.stringToDate(scalar)
+                            return date
+                        else if Utils.isNumeric(scalar)
+                            return parseFloat scalar
+                        else if @PATTERN_THOUSAND_NUMERIC_SCALAR.test scalar
+                            return parseFloat(scalar.replace(',', ''))
+                        return scalar
+
+module.exports = Inline
diff --git a/src/main/webapp/scripts/lib/yamljs/src/Parser.coffee b/src/main/webapp/scripts/lib/yamljs/src/Parser.coffee
new file mode 100755
index 0000000000000000000000000000000000000000..c0299d99dd9fb657631a12b3bcdb9c72a3b788b8
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/src/Parser.coffee
@@ -0,0 +1,654 @@
+
+Inline          = require './Inline'
+Pattern         = require './Pattern'
+Utils           = require './Utils'
+ParseException  = require './Exception/ParseException'
+
+# Parser parses YAML strings to convert them to JavaScript objects.
+#
+class Parser
+
+    # Pre-compiled patterns
+    #
+    PATTERN_FOLDED_SCALAR_ALL:              new Pattern '^(?:(?<type>![^\\|>]*)\\s+)?(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$'
+    PATTERN_FOLDED_SCALAR_END:              new Pattern '(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$'
+    PATTERN_SEQUENCE_ITEM:                  new Pattern '^\\-((?<leadspaces>\\s+)(?<value>.+?))?\\s*$'
+    PATTERN_ANCHOR_VALUE:                   new Pattern '^&(?<ref>[^ ]+) *(?<value>.*)'
+    PATTERN_COMPACT_NOTATION:               new Pattern '^(?<key>'+Inline.REGEX_QUOTED_STRING+'|[^ \'"\\{\\[].*?) *\\:(\\s+(?<value>.+?))?\\s*$'
+    PATTERN_MAPPING_ITEM:                   new Pattern '^(?<key>'+Inline.REGEX_QUOTED_STRING+'|[^ \'"\\[\\{].*?) *\\:(\\s+(?<value>.+?))?\\s*$'
+    PATTERN_DECIMAL:                        new Pattern '\\d+'
+    PATTERN_INDENT_SPACES:                  new Pattern '^ +'
+    PATTERN_TRAILING_LINES:                 new Pattern '(\n*)$'
+    PATTERN_YAML_HEADER:                    new Pattern '^\\%YAML[: ][\\d\\.]+.*\n'
+    PATTERN_LEADING_COMMENTS:               new Pattern '^(\\#.*?\n)+'
+    PATTERN_DOCUMENT_MARKER_START:          new Pattern '^\\-\\-\\-.*?\n'
+    PATTERN_DOCUMENT_MARKER_END:            new Pattern '^\\.\\.\\.\\s*$'
+    PATTERN_FOLDED_SCALAR_BY_INDENTATION:   {}
+
+    # Context types
+    #
+    CONTEXT_NONE:       0
+    CONTEXT_SEQUENCE:   1
+    CONTEXT_MAPPING:    2
+
+
+    # Constructor
+    #
+    # @param [Integer]  offset  The offset of YAML document (used for line numbers in error messages)
+    #
+    constructor: (@offset = 0) ->
+        @lines          = []
+        @currentLineNb  = -1
+        @currentLine    = ''
+        @refs           = {}
+
+
+    # Parses a YAML string to a JavaScript value.
+    #
+    # @param [String]   value                   A YAML string
+    # @param [Boolean]  exceptionOnInvalidType  true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise
+    # @param [Function] objectDecoder           A function to deserialize custom objects, null otherwise
+    #
+    # @return [Object]  A JavaScript value
+    #
+    # @throw [ParseException] If the YAML is not valid
+    #
+    parse: (value, exceptionOnInvalidType = false, objectDecoder = null) ->
+        @currentLineNb = -1
+        @currentLine = ''
+        @lines = @cleanup(value).split "\n"
+
+        data = null
+        context = @CONTEXT_NONE
+        allowOverwrite = false
+        while @moveToNextLine()
+            if @isCurrentLineEmpty()
+                continue
+
+            # Tab?
+            if "\t" is @currentLine[0]
+                throw new ParseException 'A YAML file cannot contain tabs as indentation.', @getRealCurrentLineNb() + 1, @currentLine
+
+            isRef = mergeNode = false
+            if values = @PATTERN_SEQUENCE_ITEM.exec @currentLine
+                if @CONTEXT_MAPPING is context
+                    throw new ParseException 'You cannot define a sequence item when in a mapping'
+                context = @CONTEXT_SEQUENCE
+                data ?= []
+
+                if values.value? and matches = @PATTERN_ANCHOR_VALUE.exec values.value
+                    isRef = matches.ref
+                    values.value = matches.value
+
+                # Array
+                if not(values.value?) or '' is Utils.trim(values.value, ' ') or Utils.ltrim(values.value, ' ').indexOf('#') is 0
+                    if @currentLineNb < @lines.length - 1 and not @isNextLineUnIndentedCollection()
+                        c = @getRealCurrentLineNb() + 1
+                        parser = new Parser c
+                        parser.refs = @refs
+                        data.push parser.parse(@getNextEmbedBlock(null, true), exceptionOnInvalidType, objectDecoder)
+                    else
+                        data.push null
+
+                else
+                    if values.leadspaces?.length and matches = @PATTERN_COMPACT_NOTATION.exec values.value
+
+                        # This is a compact notation element, add to next block and parse
+                        c = @getRealCurrentLineNb()
+                        parser = new Parser c
+                        parser.refs = @refs
+
+                        block = values.value
+                        indent = @getCurrentLineIndentation()
+                        if @isNextLineIndented(false)
+                            block += "\n"+@getNextEmbedBlock(indent + values.leadspaces.length + 1, true)
+
+                        data.push parser.parse block, exceptionOnInvalidType, objectDecoder
+
+                    else
+                        data.push @parseValue values.value, exceptionOnInvalidType, objectDecoder
+
+            else if (values = @PATTERN_MAPPING_ITEM.exec @currentLine) and values.key.indexOf(' #') is -1
+                if @CONTEXT_SEQUENCE is context
+                    throw new ParseException 'You cannot define a mapping item when in a sequence'
+                context = @CONTEXT_MAPPING
+                data ?= {}
+
+                # Force correct settings
+                Inline.configure exceptionOnInvalidType, objectDecoder
+                try
+                    key = Inline.parseScalar values.key
+                catch e
+                    e.parsedLine = @getRealCurrentLineNb() + 1
+                    e.snippet = @currentLine
+
+                    throw e
+
+                if '<<' is key
+                    mergeNode = true
+                    allowOverwrite = true
+                    if values.value?.indexOf('*') is 0
+                        refName = values.value[1..]
+                        unless @refs[refName]?
+                            throw new ParseException 'Reference "'+refName+'" does not exist.', @getRealCurrentLineNb() + 1, @currentLine
+
+                        refValue = @refs[refName]
+
+                        if typeof refValue isnt 'object'
+                            throw new ParseException 'YAML merge keys used with a scalar value instead of an object.', @getRealCurrentLineNb() + 1, @currentLine
+
+                        if refValue instanceof Array
+                            # Merge array with object
+                            for value, i in refValue
+                                data[String(i)] ?= value
+                        else
+                            # Merge objects
+                            for key, value of refValue
+                                data[key] ?= value
+
+                    else
+                        if values.value? and values.value isnt ''
+                            value = values.value
+                        else
+                            value = @getNextEmbedBlock()
+
+                        c = @getRealCurrentLineNb() + 1
+                        parser = new Parser c
+                        parser.refs = @refs
+                        parsed = parser.parse value, exceptionOnInvalidType
+
+                        unless typeof parsed is 'object'
+                            throw new ParseException 'YAML merge keys used with a scalar value instead of an object.', @getRealCurrentLineNb() + 1, @currentLine
+
+                        if parsed instanceof Array
+                            # If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
+                            # and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
+                            # in the sequence override keys specified in later mapping nodes.
+                            for parsedItem in parsed
+                                unless typeof parsedItem is 'object'
+                                    throw new ParseException 'Merge items must be objects.', @getRealCurrentLineNb() + 1, parsedItem
+
+                                if parsedItem instanceof Array
+                                    # Merge array with object
+                                    for value, i in parsedItem
+                                        k = String(i)
+                                        unless data.hasOwnProperty(k)
+                                            data[k] = value
+                                else
+                                    # Merge objects
+                                    for key, value of parsedItem
+                                        unless data.hasOwnProperty(key)
+                                            data[key] = value
+
+                        else
+                            # If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
+                            # current mapping, unless the key already exists in it.
+                            for key, value of parsed
+                                unless data.hasOwnProperty(key)
+                                    data[key] = value
+
+                else if values.value? and matches = @PATTERN_ANCHOR_VALUE.exec values.value
+                    isRef = matches.ref
+                    values.value = matches.value
+
+
+                if mergeNode
+                    # Merge keys
+                else if not(values.value?) or '' is Utils.trim(values.value, ' ') or Utils.ltrim(values.value, ' ').indexOf('#') is 0
+                    # Hash
+                    # if next line is less indented or equal, then it means that the current value is null
+                    if not(@isNextLineIndented()) and not(@isNextLineUnIndentedCollection())
+                        # Spec: Keys MUST be unique; first one wins.
+                        # But overwriting is allowed when a merge node is used in current block.
+                        if allowOverwrite or data[key] is undefined
+                            data[key] = null
+
+                    else
+                        c = @getRealCurrentLineNb() + 1
+                        parser = new Parser c
+                        parser.refs = @refs
+                        val = parser.parse @getNextEmbedBlock(), exceptionOnInvalidType, objectDecoder
+
+                        # Spec: Keys MUST be unique; first one wins.
+                        # But overwriting is allowed when a merge node is used in current block.
+                        if allowOverwrite or data[key] is undefined
+                            data[key] = val
+
+                else
+                    val = @parseValue values.value, exceptionOnInvalidType, objectDecoder
+
+                    # Spec: Keys MUST be unique; first one wins.
+                    # But overwriting is allowed when a merge node is used in current block.
+                    if allowOverwrite or data[key] is undefined
+                        data[key] = val
+
+            else
+                # 1-liner optionally followed by newline
+                lineCount = @lines.length
+                if 1 is lineCount or (2 is lineCount and Utils.isEmpty(@lines[1]))
+                    try
+                        value = Inline.parse @lines[0], exceptionOnInvalidType, objectDecoder
+                    catch e
+                        e.parsedLine = @getRealCurrentLineNb() + 1
+                        e.snippet = @currentLine
+
+                        throw e
+
+                    if typeof value is 'object'
+                        if value instanceof Array
+                            first = value[0]
+                        else
+                            for key of value
+                                first = value[key]
+                                break
+
+                        if typeof first is 'string' and first.indexOf('*') is 0
+                            data = []
+                            for alias in value
+                                data.push @refs[alias[1..]]
+                            value = data
+
+                    return value
+
+                else if Utils.ltrim(value).charAt(0) in ['[', '{']
+                    try
+                        return Inline.parse value, exceptionOnInvalidType, objectDecoder
+                    catch e
+                        e.parsedLine = @getRealCurrentLineNb() + 1
+                        e.snippet = @currentLine
+
+                        throw e
+
+                throw new ParseException 'Unable to parse.', @getRealCurrentLineNb() + 1, @currentLine
+
+            if isRef
+                if data instanceof Array
+                    @refs[isRef] = data[data.length-1]
+                else
+                    lastKey = null
+                    for key of data
+                        lastKey = key
+                    @refs[isRef] = data[lastKey]
+
+
+        if Utils.isEmpty(data)
+            return null
+        else
+            return data
+
+
+
+    # Returns the current line number (takes the offset into account).
+    #
+    # @return [Integer]     The current line number
+    #
+    getRealCurrentLineNb: ->
+        return @currentLineNb + @offset
+
+
+    # Returns the current line indentation.
+    #
+    # @return [Integer]     The current line indentation
+    #
+    getCurrentLineIndentation: ->
+        return @currentLine.length - Utils.ltrim(@currentLine, ' ').length
+
+
+    # Returns the next embed block of YAML.
+    #
+    # @param [Integer]          indentation The indent level at which the block is to be read, or null for default
+    #
+    # @return [String]          A YAML string
+    #
+    # @throw [ParseException]   When indentation problem are detected
+    #
+    getNextEmbedBlock: (indentation = null, includeUnindentedCollection = false) ->
+        @moveToNextLine()
+
+        if not indentation?
+            newIndent = @getCurrentLineIndentation()
+
+            unindentedEmbedBlock = @isStringUnIndentedCollectionItem @currentLine
+
+            if not(@isCurrentLineEmpty()) and 0 is newIndent and not(unindentedEmbedBlock)
+                throw new ParseException 'Indentation problem.', @getRealCurrentLineNb() + 1, @currentLine
+
+        else
+            newIndent = indentation
+
+
+        data = [@currentLine[newIndent..]]
+
+        unless includeUnindentedCollection
+            isItUnindentedCollection = @isStringUnIndentedCollectionItem @currentLine
+
+        # Comments must not be removed inside a string block (ie. after a line ending with "|")
+        # They must not be removed inside a sub-embedded block as well
+        removeCommentsPattern = @PATTERN_FOLDED_SCALAR_END
+        removeComments = not removeCommentsPattern.test @currentLine
+
+        while @moveToNextLine()
+            indent = @getCurrentLineIndentation()
+
+            if indent is newIndent
+                removeComments = not removeCommentsPattern.test @currentLine
+
+            if isItUnindentedCollection and not @isStringUnIndentedCollectionItem(@currentLine) and indent is newIndent
+                @moveToPreviousLine()
+                break
+
+            if @isCurrentLineBlank()
+                data.push @currentLine[newIndent..]
+                continue
+
+            if removeComments and @isCurrentLineComment()
+                if indent is newIndent
+                    continue
+
+            if indent >= newIndent
+                data.push @currentLine[newIndent..]
+            else if Utils.ltrim(@currentLine).charAt(0) is '#'
+                # Don't add line with comments
+            else if 0 is indent
+                @moveToPreviousLine()
+                break
+            else
+                throw new ParseException 'Indentation problem.', @getRealCurrentLineNb() + 1, @currentLine
+
+
+        return data.join "\n"
+
+
+    # Moves the parser to the next line.
+    #
+    # @return [Boolean]
+    #
+    moveToNextLine: ->
+        if @currentLineNb >= @lines.length - 1
+            return false
+
+        @currentLine = @lines[++@currentLineNb];
+
+        return true
+
+
+    # Moves the parser to the previous line.
+    #
+    moveToPreviousLine: ->
+        @currentLine = @lines[--@currentLineNb]
+        return
+
+
+    # Parses a YAML value.
+    #
+    # @param [String]   value                   A YAML value
+    # @param [Boolean]  exceptionOnInvalidType  true if an exception must be thrown on invalid types false otherwise
+    # @param [Function] objectDecoder           A function to deserialize custom objects, null otherwise
+    #
+    # @return [Object] A JavaScript value
+    #
+    # @throw [ParseException] When reference does not exist
+    #
+    parseValue: (value, exceptionOnInvalidType, objectDecoder) ->
+        if 0 is value.indexOf('*')
+            pos = value.indexOf '#'
+            if pos isnt -1
+                value = value.substr(1, pos-2)
+            else
+                value = value[1..]
+
+            if @refs[value] is undefined
+                throw new ParseException 'Reference "'+value+'" does not exist.', @currentLine
+
+            return @refs[value]
+
+
+        if matches = @PATTERN_FOLDED_SCALAR_ALL.exec value
+            modifiers = matches.modifiers ? ''
+
+            foldedIndent = Math.abs(parseInt(modifiers))
+            if isNaN(foldedIndent) then foldedIndent = 0
+            val = @parseFoldedScalar matches.separator, @PATTERN_DECIMAL.replace(modifiers, ''), foldedIndent
+            if matches.type?
+                # Force correct settings
+                Inline.configure exceptionOnInvalidType, objectDecoder
+                return Inline.parseScalar matches.type+' '+val
+            else
+                return val
+
+        try
+            return Inline.parse value, exceptionOnInvalidType, objectDecoder
+        catch e
+            # Try to parse multiline compact sequence or mapping
+            if value.charAt(0) in ['[', '{'] and e instanceof ParseException and @isNextLineIndented()
+                value += "\n" + @getNextEmbedBlock()
+                try
+                    return Inline.parse value, exceptionOnInvalidType, objectDecoder
+                catch e
+                    e.parsedLine = @getRealCurrentLineNb() + 1
+                    e.snippet = @currentLine
+
+                    throw e
+
+            else
+                e.parsedLine = @getRealCurrentLineNb() + 1
+                e.snippet = @currentLine
+
+                throw e
+
+        return
+
+
+    # Parses a folded scalar.
+    #
+    # @param [String]       separator   The separator that was used to begin this folded scalar (| or >)
+    # @param [String]       indicator   The indicator that was used to begin this folded scalar (+ or -)
+    # @param [Integer]      indentation The indentation that was used to begin this folded scalar
+    #
+    # @return [String]      The text value
+    #
+    parseFoldedScalar: (separator, indicator = '', indentation = 0) ->
+        notEOF = @moveToNextLine()
+        if not notEOF
+            return ''
+
+        isCurrentLineBlank = @isCurrentLineBlank()
+        text = ''
+
+        # Leading blank lines are consumed before determining indentation
+        while notEOF and isCurrentLineBlank
+            # newline only if not EOF
+            if notEOF = @moveToNextLine()
+                text += "\n"
+                isCurrentLineBlank = @isCurrentLineBlank()
+
+
+        # Determine indentation if not specified
+        if 0 is indentation
+            if matches = @PATTERN_INDENT_SPACES.exec @currentLine
+                indentation = matches[0].length
+
+
+        if indentation > 0
+            pattern = @PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation]
+            unless pattern?
+                pattern = new Pattern '^ {'+indentation+'}(.*)$'
+                Parser::PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation] = pattern
+
+            while notEOF and (isCurrentLineBlank or matches = pattern.exec @currentLine)
+                if isCurrentLineBlank
+                    text += @currentLine[indentation..]
+                else
+                    text += matches[1]
+
+                # newline only if not EOF
+                if notEOF = @moveToNextLine()
+                    text += "\n"
+                    isCurrentLineBlank = @isCurrentLineBlank()
+
+        else if notEOF
+            text += "\n"
+
+
+        if notEOF
+            @moveToPreviousLine()
+
+
+        # Remove line breaks of each lines except the empty and more indented ones
+        if '>' is separator
+            newText = ''
+            for line in text.split "\n"
+                if line.length is 0 or line.charAt(0) is ' '
+                    newText = Utils.rtrim(newText, ' ') + line + "\n"
+                else
+                    newText += line + ' '
+            text = newText
+
+        if '+' isnt indicator
+            # Remove any extra space or new line as we are adding them after
+            text = Utils.rtrim(text)
+
+        # Deal with trailing newlines as indicated
+        if '' is indicator
+            text = @PATTERN_TRAILING_LINES.replace text, "\n"
+        else if '-' is indicator
+            text = @PATTERN_TRAILING_LINES.replace text, ''
+
+        return text
+
+
+    # Returns true if the next line is indented.
+    #
+    # @return [Boolean]     Returns true if the next line is indented, false otherwise
+    #
+    isNextLineIndented: (ignoreComments = true) ->
+        currentIndentation = @getCurrentLineIndentation()
+        EOF = not @moveToNextLine()
+
+        if ignoreComments
+            while not(EOF) and @isCurrentLineEmpty()
+                EOF = not @moveToNextLine()
+        else
+            while not(EOF) and @isCurrentLineBlank()
+                EOF = not @moveToNextLine()
+
+        if EOF
+            return false
+
+        ret = false
+        if @getCurrentLineIndentation() > currentIndentation
+            ret = true
+
+        @moveToPreviousLine()
+
+        return ret
+
+
+    # Returns true if the current line is blank or if it is a comment line.
+    #
+    # @return [Boolean]     Returns true if the current line is empty or if it is a comment line, false otherwise
+    #
+    isCurrentLineEmpty: ->
+        trimmedLine = Utils.trim(@currentLine, ' ')
+        return trimmedLine.length is 0 or trimmedLine.charAt(0) is '#'
+
+
+    # Returns true if the current line is blank.
+    #
+    # @return [Boolean]     Returns true if the current line is blank, false otherwise
+    #
+    isCurrentLineBlank: ->
+        return '' is Utils.trim(@currentLine, ' ')
+
+
+    # Returns true if the current line is a comment line.
+    #
+    # @return [Boolean]     Returns true if the current line is a comment line, false otherwise
+    #
+    isCurrentLineComment: ->
+        # Checking explicitly the first char of the trim is faster than loops or strpos
+        ltrimmedLine = Utils.ltrim(@currentLine, ' ')
+
+        return ltrimmedLine.charAt(0) is '#'
+
+
+    # Cleanups a YAML string to be parsed.
+    #
+    # @param [String]   value The input YAML string
+    #
+    # @return [String]  A cleaned up YAML string
+    #
+    cleanup: (value) ->
+        if value.indexOf("\r") isnt -1
+            value = value.split("\r\n").join("\n").split("\r").join("\n")
+
+        # Strip YAML header
+        count = 0
+        [value, count] = @PATTERN_YAML_HEADER.replaceAll value, ''
+        @offset += count
+
+        # Remove leading comments
+        [trimmedValue, count] = @PATTERN_LEADING_COMMENTS.replaceAll value, '', 1
+        if count is 1
+            # Items have been removed, update the offset
+            @offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n")
+            value = trimmedValue
+
+        # Remove start of the document marker (---)
+        [trimmedValue, count] = @PATTERN_DOCUMENT_MARKER_START.replaceAll value, '', 1
+        if count is 1
+            # Items have been removed, update the offset
+            @offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n")
+            value = trimmedValue
+
+            # Remove end of the document marker (...)
+            value = @PATTERN_DOCUMENT_MARKER_END.replace value, ''
+
+        # Ensure the block is not indented
+        lines = value.split("\n")
+        smallestIndent = -1
+        for line in lines
+            continue if Utils.trim(line, ' ').length == 0
+            indent = line.length - Utils.ltrim(line).length
+            if smallestIndent is -1 or indent < smallestIndent
+                smallestIndent = indent
+        if smallestIndent > 0
+            for line, i in lines
+                lines[i] = line[smallestIndent..]
+            value = lines.join("\n")
+
+        return value
+
+
+    # Returns true if the next line starts unindented collection
+    #
+    # @return [Boolean]     Returns true if the next line starts unindented collection, false otherwise
+    #
+    isNextLineUnIndentedCollection: (currentIndentation = null) ->
+        currentIndentation ?= @getCurrentLineIndentation()
+        notEOF = @moveToNextLine()
+
+        while notEOF and @isCurrentLineEmpty()
+            notEOF = @moveToNextLine()
+
+        if false is notEOF
+            return false
+
+        ret = false
+        if @getCurrentLineIndentation() is currentIndentation and @isStringUnIndentedCollectionItem(@currentLine)
+            ret = true
+
+        @moveToPreviousLine()
+
+        return ret
+
+
+    # Returns true if the string is un-indented collection item
+    #
+    # @return [Boolean]     Returns true if the string is un-indented collection item, false otherwise
+    #
+    isStringUnIndentedCollectionItem: ->
+        return @currentLine is '-' or @currentLine[0...2] is '- '
+
+
+module.exports = Parser
diff --git a/src/main/webapp/scripts/lib/yamljs/src/Pattern.coffee b/src/main/webapp/scripts/lib/yamljs/src/Pattern.coffee
new file mode 100755
index 0000000000000000000000000000000000000000..e65fa793a87028ca781a03240309321f15efacec
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/src/Pattern.coffee
@@ -0,0 +1,144 @@
+
+# Pattern is a zero-conflict wrapper extending RegExp features
+# in order to make YAML parsing regex more expressive.
+#
+class Pattern
+
+    # @property [RegExp] The RegExp instance
+    regex:          null
+
+    # @property [String] The raw regex string
+    rawRegex:       null
+
+    # @property [String] The cleaned regex string (used to create the RegExp instance)
+    cleanedRegex:   null
+
+    # @property [Object] The dictionary mapping names to capturing bracket numbers
+    mapping:        null
+
+    # Constructor
+    #
+    # @param [String] rawRegex The raw regex string defining the pattern
+    #
+    constructor: (rawRegex, modifiers = '') ->
+        cleanedRegex = ''
+        len = rawRegex.length
+        mapping = null
+
+        # Cleanup raw regex and compute mapping
+        capturingBracketNumber = 0
+        i = 0
+        while i < len
+            _char = rawRegex.charAt(i)
+            if _char is '\\'
+                # Ignore next character
+                cleanedRegex += rawRegex[i..i+1]
+                i++
+            else if _char is '('
+                # Increase bracket number, only if it is capturing
+                if i < len - 2
+                    part = rawRegex[i..i+2]
+                    if part is '(?:'
+                        # Non-capturing bracket
+                        i += 2
+                        cleanedRegex += part
+                    else if part is '(?<'
+                        # Capturing bracket with possibly a name
+                        capturingBracketNumber++
+                        i += 2
+                        name = ''
+                        while i + 1 < len
+                            subChar = rawRegex.charAt(i + 1)
+                            if subChar is '>'
+                                cleanedRegex += '('
+                                i++
+                                if name.length > 0
+                                    # Associate a name with a capturing bracket number
+                                    mapping ?= {}
+                                    mapping[name] = capturingBracketNumber
+                                break
+                            else
+                                name += subChar
+
+                            i++
+                    else
+                        cleanedRegex += _char
+                        capturingBracketNumber++
+                else
+                    cleanedRegex += _char
+            else
+                cleanedRegex += _char
+
+            i++
+
+        @rawRegex = rawRegex
+        @cleanedRegex = cleanedRegex
+        @regex = new RegExp @cleanedRegex, 'g'+modifiers.replace('g', '')
+        @mapping = mapping
+
+
+    # Executes the pattern's regex and returns the matching values
+    #
+    # @param [String] str The string to use to execute the pattern
+    #
+    # @return [Array] The matching values extracted from capturing brackets or null if nothing matched
+    #
+    exec: (str) ->
+        @regex.lastIndex = 0
+        matches = @regex.exec str
+
+        if not matches?
+            return null
+
+        if @mapping?
+            for name, index of @mapping
+                matches[name] = matches[index]
+
+        return matches
+
+
+    # Tests the pattern's regex
+    #
+    # @param [String] str The string to use to test the pattern
+    #
+    # @return [Boolean] true if the string matched
+    #
+    test: (str) ->
+        @regex.lastIndex = 0
+        return @regex.test str
+
+
+    # Replaces occurences matching with the pattern's regex with replacement
+    #
+    # @param [String] str The source string to perform replacements
+    # @param [String] replacement The string to use in place of each replaced occurence.
+    #
+    # @return [String] The replaced string
+    #
+    replace: (str, replacement) ->
+        @regex.lastIndex = 0
+        return str.replace @regex, replacement
+
+
+    # Replaces occurences matching with the pattern's regex with replacement and
+    # get both the replaced string and the number of replaced occurences in the string.
+    #
+    # @param [String] str The source string to perform replacements
+    # @param [String] replacement The string to use in place of each replaced occurence.
+    # @param [Integer] limit The maximum number of occurences to replace (0 means infinite number of occurences)
+    #
+    # @return [Array] A destructurable array containing the replaced string and the number of replaced occurences. For instance: ["my replaced string", 2]
+    #
+    replaceAll: (str, replacement, limit = 0) ->
+        @regex.lastIndex = 0
+        count = 0
+        while @regex.test(str) and (limit is 0 or count < limit)
+            @regex.lastIndex = 0
+            str = str.replace @regex, ''
+            count++
+        
+        return [str, count]
+
+
+module.exports = Pattern
+
diff --git a/src/main/webapp/scripts/lib/yamljs/src/Unescaper.coffee b/src/main/webapp/scripts/lib/yamljs/src/Unescaper.coffee
new file mode 100755
index 0000000000000000000000000000000000000000..8e1527a73809284d11076e2e3408832df77afab1
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/src/Unescaper.coffee
@@ -0,0 +1,96 @@
+
+Utils   = require './Utils'
+Pattern = require './Pattern'
+
+# Unescaper encapsulates unescaping rules for single and double-quoted YAML strings.
+#
+class Unescaper
+
+    # Regex fragment that matches an escaped character in
+    # a double quoted string.
+    @PATTERN_ESCAPED_CHARACTER:     new Pattern '\\\\([0abt\tnvfre "\\/\\\\N_LP]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})';
+
+
+    # Unescapes a single quoted string.
+    #
+    # @param [String]       value A single quoted string.
+    #
+    # @return [String]      The unescaped string.
+    #
+    @unescapeSingleQuotedString: (value) ->
+        return value.replace(/\'\'/g, '\'')
+
+
+    # Unescapes a double quoted string.
+    #
+    # @param [String]       value A double quoted string.
+    #
+    # @return [String]      The unescaped string.
+    #
+    @unescapeDoubleQuotedString: (value) ->
+        @_unescapeCallback ?= (str) =>
+            return @unescapeCharacter(str)
+
+        # Evaluate the string
+        return @PATTERN_ESCAPED_CHARACTER.replace value, @_unescapeCallback
+
+
+    # Unescapes a character that was found in a double-quoted string
+    #
+    # @param [String]       value An escaped character
+    #
+    # @return [String]      The unescaped character
+    #
+    @unescapeCharacter: (value) ->
+        ch = String.fromCharCode
+        switch value.charAt(1)
+            when '0'
+                return ch(0)
+            when 'a'
+                return ch(7)
+            when 'b'
+                return ch(8)
+            when 't'
+                return "\t"
+            when "\t"
+                return "\t"
+            when 'n'
+                return "\n"
+            when 'v'
+                return ch(11)
+            when 'f'
+                return ch(12)
+            when 'r'
+                return ch(13)
+            when 'e'
+                return ch(27)
+            when ' '
+                return ' '
+            when '"'
+                return '"'
+            when '/'
+                return '/'
+            when '\\'
+                return '\\'
+            when 'N'
+                # U+0085 NEXT LINE
+                return ch(0x0085)
+            when '_'
+                # U+00A0 NO-BREAK SPACE
+                return ch(0x00A0)
+            when 'L'
+                # U+2028 LINE SEPARATOR
+                return ch(0x2028)
+            when 'P'
+                # U+2029 PARAGRAPH SEPARATOR
+                return ch(0x2029)
+            when 'x'
+                return Utils.utf8chr(Utils.hexDec(value.substr(2, 2)))
+            when 'u'
+                return Utils.utf8chr(Utils.hexDec(value.substr(2, 4)))
+            when 'U'
+                return Utils.utf8chr(Utils.hexDec(value.substr(2, 8)))
+            else
+                return ''
+
+module.exports = Unescaper
diff --git a/src/main/webapp/scripts/lib/yamljs/src/Utils.coffee b/src/main/webapp/scripts/lib/yamljs/src/Utils.coffee
new file mode 100755
index 0000000000000000000000000000000000000000..6ab111617b9b12ab30d44f00bfa8c4e19cc1c96c
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/src/Utils.coffee
@@ -0,0 +1,342 @@
+
+Pattern = require './Pattern'
+
+# A bunch of utility methods
+#
+class Utils
+
+    @REGEX_LEFT_TRIM_BY_CHAR:   {}
+    @REGEX_RIGHT_TRIM_BY_CHAR:  {}
+    @REGEX_SPACES:              /\s+/g
+    @REGEX_DIGITS:              /^\d+$/
+    @REGEX_OCTAL:               /[^0-7]/gi
+    @REGEX_HEXADECIMAL:         /[^a-f0-9]/gi
+
+    # Precompiled date pattern
+    @PATTERN_DATE:              new Pattern '^'+
+            '(?<year>[0-9][0-9][0-9][0-9])'+
+            '-(?<month>[0-9][0-9]?)'+
+            '-(?<day>[0-9][0-9]?)'+
+            '(?:(?:[Tt]|[ \t]+)'+
+            '(?<hour>[0-9][0-9]?)'+
+            ':(?<minute>[0-9][0-9])'+
+            ':(?<second>[0-9][0-9])'+
+            '(?:\.(?<fraction>[0-9]*))?'+
+            '(?:[ \t]*(?<tz>Z|(?<tz_sign>[-+])(?<tz_hour>[0-9][0-9]?)'+
+            '(?::(?<tz_minute>[0-9][0-9]))?))?)?'+
+            '$', 'i'
+
+    # Local timezone offset in ms
+    @LOCAL_TIMEZONE_OFFSET:     new Date().getTimezoneOffset() * 60 * 1000
+
+    # Trims the given string on both sides
+    #
+    # @param [String] str The string to trim
+    # @param [String] _char The character to use for trimming (default: '\\s')
+    #
+    # @return [String] A trimmed string
+    #
+    @trim: (str, _char = '\\s') ->
+        return str.trim()
+        regexLeft = @REGEX_LEFT_TRIM_BY_CHAR[_char]
+        unless regexLeft?
+            @REGEX_LEFT_TRIM_BY_CHAR[_char] = regexLeft = new RegExp '^'+_char+''+_char+'*'
+        regexLeft.lastIndex = 0
+        regexRight = @REGEX_RIGHT_TRIM_BY_CHAR[_char]
+        unless regexRight?
+            @REGEX_RIGHT_TRIM_BY_CHAR[_char] = regexRight = new RegExp _char+''+_char+'*$'
+        regexRight.lastIndex = 0
+        return str.replace(regexLeft, '').replace(regexRight, '')
+
+
+    # Trims the given string on the left side
+    #
+    # @param [String] str The string to trim
+    # @param [String] _char The character to use for trimming (default: '\\s')
+    #
+    # @return [String] A trimmed string
+    #
+    @ltrim: (str, _char = '\\s') ->
+        regexLeft = @REGEX_LEFT_TRIM_BY_CHAR[_char]
+        unless regexLeft?
+            @REGEX_LEFT_TRIM_BY_CHAR[_char] = regexLeft = new RegExp '^'+_char+''+_char+'*'
+        regexLeft.lastIndex = 0
+        return str.replace(regexLeft, '')
+
+
+    # Trims the given string on the right side
+    #
+    # @param [String] str The string to trim
+    # @param [String] _char The character to use for trimming (default: '\\s')
+    #
+    # @return [String] A trimmed string
+    #
+    @rtrim: (str, _char = '\\s') ->
+        regexRight = @REGEX_RIGHT_TRIM_BY_CHAR[_char]
+        unless regexRight?
+            @REGEX_RIGHT_TRIM_BY_CHAR[_char] = regexRight = new RegExp _char+''+_char+'*$'
+        regexRight.lastIndex = 0
+        return str.replace(regexRight, '')
+
+
+    # Checks if the given value is empty (null, undefined, empty string, string '0')
+    #
+    # @param [Object] value The value to check
+    #
+    # @return [Boolean] true if the value is empty
+    #
+    @isEmpty: (value) ->
+        return not(value) or value is '' or value is '0' or (value instanceof Array and value.length is 0)
+
+
+    # Counts the number of occurences of subString inside string
+    #
+    # @param [String] string The string where to count occurences
+    # @param [String] subString The subString to count
+    # @param [Integer] start The start index
+    # @param [Integer] length The string length until where to count
+    #
+    # @return [Integer] The number of occurences
+    #
+    @subStrCount: (string, subString, start, length) ->
+        c = 0
+        
+        string = '' + string
+        subString = '' + subString
+        
+        if start?
+            string = string[start..]
+        if length?
+            string = string[0...length]
+        
+        len = string.length
+        sublen = subString.length
+        for i in [0...len]
+            if subString is string[i...sublen]
+                c++
+                i += sublen - 1
+        
+        return c
+
+
+    # Returns true if input is only composed of digits
+    #
+    # @param [Object] input The value to test
+    #
+    # @return [Boolean] true if input is only composed of digits
+    #
+    @isDigits: (input) ->
+        @REGEX_DIGITS.lastIndex = 0
+        return @REGEX_DIGITS.test input
+
+
+    # Decode octal value
+    #
+    # @param [String] input The value to decode
+    #
+    # @return [Integer] The decoded value
+    #
+    @octDec: (input) ->
+        @REGEX_OCTAL.lastIndex = 0
+        return parseInt((input+'').replace(@REGEX_OCTAL, ''), 8)
+
+
+    # Decode hexadecimal value
+    #
+    # @param [String] input The value to decode
+    #
+    # @return [Integer] The decoded value
+    #
+    @hexDec: (input) ->
+        @REGEX_HEXADECIMAL.lastIndex = 0
+        input = @trim(input)
+        if (input+'')[0...2] is '0x' then input = (input+'')[2..]
+        return parseInt((input+'').replace(@REGEX_HEXADECIMAL, ''), 16)
+
+
+    # Get the UTF-8 character for the given code point.
+    #
+    # @param [Integer] c The unicode code point
+    #
+    # @return [String] The corresponding UTF-8 character
+    #
+    @utf8chr: (c) ->
+        ch = String.fromCharCode
+        if 0x80 > (c %= 0x200000)
+            return ch(c)
+        if 0x800 > c
+            return ch(0xC0 | c>>6) + ch(0x80 | c & 0x3F)
+        if 0x10000 > c
+            return ch(0xE0 | c>>12) + ch(0x80 | c>>6 & 0x3F) + ch(0x80 | c & 0x3F)
+
+        return ch(0xF0 | c>>18) + ch(0x80 | c>>12 & 0x3F) + ch(0x80 | c>>6 & 0x3F) + ch(0x80 | c & 0x3F)
+
+
+    # Returns the boolean value equivalent to the given input
+    #
+    # @param [String|Object]    input       The input value
+    # @param [Boolean]          strict      If set to false, accept 'yes' and 'no' as boolean values
+    #
+    # @return [Boolean]         the boolean value
+    #
+    @parseBoolean: (input, strict = true) ->
+        if typeof(input) is 'string'
+            lowerInput = input.toLowerCase()
+            if not strict
+                if lowerInput is 'no' then return false
+            if lowerInput is '0' then return false
+            if lowerInput is 'false' then return false
+            if lowerInput is '' then return false
+            return true
+        return !!input
+
+
+
+    # Returns true if input is numeric
+    #
+    # @param [Object] input The value to test
+    #
+    # @return [Boolean] true if input is numeric
+    #
+    @isNumeric: (input) ->
+        @REGEX_SPACES.lastIndex = 0
+        return typeof(input) is 'number' or typeof(input) is 'string' and !isNaN(input) and input.replace(@REGEX_SPACES, '') isnt ''
+
+
+    # Returns a parsed date from the given string
+    #
+    # @param [String] str The date string to parse
+    #
+    # @return [Date] The parsed date or null if parsing failed
+    #
+    @stringToDate: (str) ->
+        unless str?.length
+            return null
+
+        # Perform regular expression pattern
+        info = @PATTERN_DATE.exec str
+        unless info
+            return null
+
+        # Extract year, month, day
+        year = parseInt info.year, 10
+        month = parseInt(info.month, 10) - 1 # In javascript, january is 0, february 1, etc...
+        day = parseInt info.day, 10
+
+        # If no hour is given, return a date with day precision
+        unless info.hour?
+            date = new Date Date.UTC(year, month, day)
+            return date
+
+        # Extract hour, minute, second
+        hour = parseInt info.hour, 10
+        minute = parseInt info.minute, 10
+        second = parseInt info.second, 10
+
+        # Extract fraction, if given
+        if info.fraction?
+            fraction = info.fraction[0...3]
+            while fraction.length < 3
+                fraction += '0'
+            fraction = parseInt fraction, 10
+        else
+            fraction = 0
+
+        # Compute timezone offset if given
+        if info.tz?
+            tz_hour = parseInt info.tz_hour, 10
+            if info.tz_minute?
+                tz_minute = parseInt info.tz_minute, 10
+            else
+                tz_minute = 0
+
+            # Compute timezone delta in ms
+            tz_offset = (tz_hour * 60 + tz_minute) * 60000
+            if '-' is info.tz_sign
+                tz_offset *= -1
+
+        # Compute date
+        date = new Date Date.UTC(year, month, day, hour, minute, second, fraction)
+        if tz_offset
+            date.setTime date.getTime() + tz_offset
+
+        return date
+
+
+    # Repeats the given string a number of times
+    #
+    # @param [String]   str     The string to repeat
+    # @param [Integer]  number  The number of times to repeat the string
+    #
+    # @return [String]  The repeated string
+    #
+    @strRepeat: (str, number) ->
+        res = ''
+        i = 0
+        while i < number
+            res += str
+            i++
+        return res
+
+
+    # Reads the data from the given file path and returns the result as string
+    #
+    # @param [String]   path        The path to the file
+    # @param [Function] callback    A callback to read file asynchronously (optional)
+    #
+    # @return [String]  The resulting data as string
+    #
+    @getStringFromFile: (path, callback = null) ->
+        xhr = null
+        if window?
+            if window.XMLHttpRequest
+                xhr = new XMLHttpRequest()
+            else if window.ActiveXObject
+                for name in ["Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.3.0", "Msxml2.XMLHTTP", "Microsoft.XMLHTTP"]
+                    try
+                        xhr = new ActiveXObject(name)
+
+        if xhr?
+            # Browser
+            if callback?
+                # Async
+                xhr.onreadystatechange = ->
+                    if xhr.readyState is 4
+                        if xhr.status is 200 or xhr.status is 0
+                            callback(xhr.responseText)
+                        else
+                            callback(null)
+                xhr.open 'GET', path, true
+                xhr.send null
+            
+            else
+                # Sync
+                xhr.open 'GET', path, false
+                xhr.send null
+
+                if xhr.status is 200 or xhr.status == 0
+                    return xhr.responseText
+
+                return null
+        else
+            # Node.js-like
+            req = require
+            fs = req('fs') # Prevent browserify from trying to load 'fs' module
+            if callback?
+                # Async
+                fs.readFile path, (err, data) ->
+                    if err
+                        callback null
+                    else
+                        callback String(data)
+
+            else
+                # Sync
+                data = fs.readFileSync path
+                if data?
+                    return String(data)
+                return null
+
+
+
+module.exports = Utils
diff --git a/src/main/webapp/scripts/lib/yamljs/src/Yaml.coffee b/src/main/webapp/scripts/lib/yamljs/src/Yaml.coffee
new file mode 100755
index 0000000000000000000000000000000000000000..c3ae8ae16dd1e58b0518fa702ca83f626cca5e6b
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/src/Yaml.coffee
@@ -0,0 +1,118 @@
+
+Parser = require './Parser'
+Dumper = require './Dumper'
+Utils  = require './Utils'
+
+# Yaml offers convenience methods to load and dump YAML.
+#
+class Yaml
+
+    # Parses YAML into a JavaScript object.
+    #
+    # The parse method, when supplied with a YAML string,
+    # will do its best to convert YAML in a file into a JavaScript object.
+    #
+    #  Usage:
+    #     myObject = Yaml.parse('some: yaml');
+    #     console.log(myObject);
+    #
+    # @param [String]   input                   A string containing YAML
+    # @param [Boolean]  exceptionOnInvalidType  true if an exception must be thrown on invalid types, false otherwise
+    # @param [Function] objectDecoder           A function to deserialize custom objects, null otherwise
+    #
+    # @return [Object]  The YAML converted to a JavaScript object
+    #
+    # @throw [ParseException] If the YAML is not valid
+    #
+    @parse: (input, exceptionOnInvalidType = false, objectDecoder = null) ->
+        return new Parser().parse(input, exceptionOnInvalidType, objectDecoder)
+
+
+    # Parses YAML from file path into a JavaScript object.
+    #
+    # The parseFile method, when supplied with a YAML file,
+    # will do its best to convert YAML in a file into a JavaScript object.
+    #
+    #  Usage:
+    #     myObject = Yaml.parseFile('config.yml');
+    #     console.log(myObject);
+    #
+    # @param [String]   path                    A file path pointing to a valid YAML file
+    # @param [Boolean]  exceptionOnInvalidType  true if an exception must be thrown on invalid types, false otherwise
+    # @param [Function] objectDecoder           A function to deserialize custom objects, null otherwise
+    #
+    # @return [Object]  The YAML converted to a JavaScript object or null if the file doesn't exist.
+    #
+    # @throw [ParseException] If the YAML is not valid
+    #
+    @parseFile: (path, callback = null, exceptionOnInvalidType = false, objectDecoder = null) ->
+        if callback?
+            # Async
+            Utils.getStringFromFile path, (input) =>
+                result = null
+                if input?
+                    result = @parse input, exceptionOnInvalidType, objectDecoder
+                callback result
+                return
+        else
+            # Sync
+            input = Utils.getStringFromFile path
+            if input?
+                return @parse input, exceptionOnInvalidType, objectDecoder
+            return null
+
+
+    # Dumps a JavaScript object to a YAML string.
+    #
+    # The dump method, when supplied with an object, will do its best
+    # to convert the object into friendly YAML.
+    #
+    # @param [Object]   input                   JavaScript object
+    # @param [Integer]  inline                  The level where you switch to inline YAML
+    # @param [Integer]  indent                  The amount of spaces to use for indentation of nested nodes.
+    # @param [Boolean]  exceptionOnInvalidType  true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise
+    # @param [Function] objectEncoder           A function to serialize custom objects, null otherwise
+    #
+    # @return [String]  A YAML string representing the original JavaScript object
+    #
+    @dump: (input, inline = 2, indent = 4, exceptionOnInvalidType = false, objectEncoder = null) ->
+        yaml = new Dumper()
+        yaml.indentation = indent
+
+        return yaml.dump(input, inline, 0, exceptionOnInvalidType, objectEncoder)
+
+
+    # Registers .yml extension to work with node's require() function.
+    #
+    @register: ->
+        require_handler = (module, filename) ->
+            # Fill in result
+            module.exports = YAML.parseFile filename
+
+        # Register require extensions only if we're on node.js
+        # hack for browserify
+        if require?.extensions?
+            require.extensions['.yml'] = require_handler
+            require.extensions['.yaml'] = require_handler
+
+
+    # Alias of dump() method for compatibility reasons.
+    #
+    @stringify: (input, inline, indent, exceptionOnInvalidType, objectEncoder) ->
+        return @dump input, inline, indent, exceptionOnInvalidType, objectEncoder
+
+
+    # Alias of parseFile() method for compatibility reasons.
+    #
+    @load: (path, callback, exceptionOnInvalidType, objectDecoder) ->
+        return @parseFile path, callback, exceptionOnInvalidType, objectDecoder
+
+
+# Expose YAML namespace to browser
+window?.YAML = Yaml
+
+# Not in the browser?
+unless window?
+    @YAML = Yaml
+
+module.exports = Yaml
diff --git a/src/main/webapp/scripts/lib/yamljs/test/SpecRunner.html b/src/main/webapp/scripts/lib/yamljs/test/SpecRunner.html
new file mode 100755
index 0000000000000000000000000000000000000000..840545d47c7b53e02d1535698382d97b03c3d2c0
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/test/SpecRunner.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  <title>Jasmine Spec Runner v2.0.0</title>
+
+  <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.0.0/jasmine_favicon.png">
+  <link rel="stylesheet" type="text/css" href="lib/jasmine-2.0.0/jasmine.css">
+
+  <script type="text/javascript" src="lib/jasmine-2.0.0/jasmine.js"></script>
+  <script type="text/javascript" src="lib/jasmine-2.0.0/jasmine-html.js"></script>
+  <script type="text/javascript" src="lib/jasmine-2.0.0/boot.js"></script>
+
+  <!-- include source files here... -->
+  <script type="text/javascript" src="../dist/yaml.debug.js"></script>
+
+  <!-- include spec files here... -->
+  <script type="text/javascript" src="spec/YamlSpec.js"></script>
+
+</head>
+
+<body>
+</body>
+</html>
diff --git a/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/MIT.LICENSE b/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/MIT.LICENSE
new file mode 100755
index 0000000000000000000000000000000000000000..7c435baaec86c0ebe2eb56b0550c11820c181b05
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/MIT.LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008-2011 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/boot.js b/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/boot.js
new file mode 100755
index 0000000000000000000000000000000000000000..ec8baa0aa593aa0d36c83419698e13efb02ea9a2
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/boot.js
@@ -0,0 +1,181 @@
+/**
+ Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project.
+
+ If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms.
+
+ The location of `boot.js` can be specified and/or overridden in `jasmine.yml`.
+
+ [jasmine-gem]: http://github.com/pivotal/jasmine-gem
+ */
+
+(function() {
+
+  /**
+   * ## Require &amp; Instantiate
+   *
+   * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
+   */
+  window.jasmine = jasmineRequire.core(jasmineRequire);
+
+  /**
+   * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference.
+   */
+  jasmineRequire.html(jasmine);
+
+  /**
+   * Create the Jasmine environment. This is used to run all specs in a project.
+   */
+  var env = jasmine.getEnv();
+
+  /**
+   * ## The Global Interface
+   *
+   * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged.
+   */
+  var jasmineInterface = {
+    describe: function(description, specDefinitions) {
+      return env.describe(description, specDefinitions);
+    },
+
+    xdescribe: function(description, specDefinitions) {
+      return env.xdescribe(description, specDefinitions);
+    },
+
+    it: function(desc, func) {
+      return env.it(desc, func);
+    },
+
+    xit: function(desc, func) {
+      return env.xit(desc, func);
+    },
+
+    beforeEach: function(beforeEachFunction) {
+      return env.beforeEach(beforeEachFunction);
+    },
+
+    afterEach: function(afterEachFunction) {
+      return env.afterEach(afterEachFunction);
+    },
+
+    expect: function(actual) {
+      return env.expect(actual);
+    },
+
+    pending: function() {
+      return env.pending();
+    },
+
+    spyOn: function(obj, methodName) {
+      return env.spyOn(obj, methodName);
+    },
+
+    jsApiReporter: new jasmine.JsApiReporter({
+      timer: new jasmine.Timer()
+    })
+  };
+
+  /**
+   * Add all of the Jasmine global/public interface to the proper global, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
+   */
+  if (typeof window == "undefined" && typeof exports == "object") {
+    extend(exports, jasmineInterface);
+  } else {
+    extend(window, jasmineInterface);
+  }
+
+  /**
+   * Expose the interface for adding custom equality testers.
+   */
+  jasmine.addCustomEqualityTester = function(tester) {
+    env.addCustomEqualityTester(tester);
+  };
+
+  /**
+   * Expose the interface for adding custom expectation matchers
+   */
+  jasmine.addMatchers = function(matchers) {
+    return env.addMatchers(matchers);
+  };
+
+  /**
+   * Expose the mock interface for the JavaScript timeout functions
+   */
+  jasmine.clock = function() {
+    return env.clock;
+  };
+
+  /**
+   * ## Runner Parameters
+   *
+   * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface.
+   */
+
+  var queryString = new jasmine.QueryString({
+    getWindowLocation: function() { return window.location; }
+  });
+
+  var catchingExceptions = queryString.getParam("catch");
+  env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions);
+
+  /**
+   * ## Reporters
+   * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any).
+   */
+  var htmlReporter = new jasmine.HtmlReporter({
+    env: env,
+    onRaiseExceptionsClick: function() { queryString.setParam("catch", !env.catchingExceptions()); },
+    getContainer: function() { return document.body; },
+    createElement: function() { return document.createElement.apply(document, arguments); },
+    createTextNode: function() { return document.createTextNode.apply(document, arguments); },
+    timer: new jasmine.Timer()
+  });
+
+  /**
+   * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results  from JavaScript.
+   */
+  env.addReporter(jasmineInterface.jsApiReporter);
+  env.addReporter(htmlReporter);
+
+  /**
+   * Filter which specs will be run by matching the start of the full name against the `spec` query param.
+   */
+  var specFilter = new jasmine.HtmlSpecFilter({
+    filterString: function() { return queryString.getParam("spec"); }
+  });
+
+  env.specFilter = function(spec) {
+    return specFilter.matches(spec.getFullName());
+  };
+
+  /**
+   * Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack.
+   */
+  window.setTimeout = window.setTimeout;
+  window.setInterval = window.setInterval;
+  window.clearTimeout = window.clearTimeout;
+  window.clearInterval = window.clearInterval;
+
+  /**
+   * ## Execution
+   *
+   * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
+   */
+  var currentWindowOnload = window.onload;
+
+  window.onload = function() {
+    if (currentWindowOnload) {
+      currentWindowOnload();
+    }
+    htmlReporter.initialize();
+    env.execute();
+  };
+
+  /**
+   * Helper function for readability above.
+   */
+  function extend(destination, source) {
+    for (var property in source) destination[property] = source[property];
+    return destination;
+  }
+
+}());
diff --git a/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/console.js b/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/console.js
new file mode 100755
index 0000000000000000000000000000000000000000..33c1698cf14b7045b11c81a809bacea2f23c9e95
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/console.js
@@ -0,0 +1,160 @@
+/*
+Copyright (c) 2008-2013 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+function getJasmineRequireObj() {
+  if (typeof module !== "undefined" && module.exports) {
+    return exports;
+  } else {
+    window.jasmineRequire = window.jasmineRequire || {};
+    return window.jasmineRequire;
+  }
+}
+
+getJasmineRequireObj().console = function(jRequire, j$) {
+  j$.ConsoleReporter = jRequire.ConsoleReporter();
+};
+
+getJasmineRequireObj().ConsoleReporter = function() {
+
+  var noopTimer = {
+    start: function(){},
+    elapsed: function(){ return 0; }
+  };
+
+  function ConsoleReporter(options) {
+    var print = options.print,
+      showColors = options.showColors || false,
+      onComplete = options.onComplete || function() {},
+      timer = options.timer || noopTimer,
+      specCount,
+      failureCount,
+      failedSpecs = [],
+      pendingCount,
+      ansi = {
+        green: '\x1B[32m',
+        red: '\x1B[31m',
+        yellow: '\x1B[33m',
+        none: '\x1B[0m'
+      };
+
+    this.jasmineStarted = function() {
+      specCount = 0;
+      failureCount = 0;
+      pendingCount = 0;
+      print("Started");
+      printNewline();
+      timer.start();
+    };
+
+    this.jasmineDone = function() {
+      printNewline();
+      for (var i = 0; i < failedSpecs.length; i++) {
+        specFailureDetails(failedSpecs[i]);
+      }
+
+      printNewline();
+      var specCounts = specCount + " " + plural("spec", specCount) + ", " +
+        failureCount + " " + plural("failure", failureCount);
+
+      if (pendingCount) {
+        specCounts += ", " + pendingCount + " pending " + plural("spec", pendingCount);
+      }
+
+      print(specCounts);
+
+      printNewline();
+      var seconds = timer.elapsed() / 1000;
+      print("Finished in " + seconds + " " + plural("second", seconds));
+
+      printNewline();
+
+      onComplete(failureCount === 0);
+    };
+
+    this.specDone = function(result) {
+      specCount++;
+
+      if (result.status == "pending") {
+        pendingCount++;
+        print(colored("yellow", "*"));
+        return;
+      }
+
+      if (result.status == "passed") {
+        print(colored("green", '.'));
+        return;
+      }
+
+      if (result.status == "failed") {
+        failureCount++;
+        failedSpecs.push(result);
+        print(colored("red", 'F'));
+      }
+    };
+
+    return this;
+
+    function printNewline() {
+      print("\n");
+    }
+
+    function colored(color, str) {
+      return showColors ? (ansi[color] + str + ansi.none) : str;
+    }
+
+    function plural(str, count) {
+      return count == 1 ? str : str + "s";
+    }
+
+    function repeat(thing, times) {
+      var arr = [];
+      for (var i = 0; i < times; i++) {
+        arr.push(thing);
+      }
+      return arr;
+    }
+
+    function indent(str, spaces) {
+      var lines = (str || '').split("\n");
+      var newArr = [];
+      for (var i = 0; i < lines.length; i++) {
+        newArr.push(repeat(" ", spaces).join("") + lines[i]);
+      }
+      return newArr.join("\n");
+    }
+
+    function specFailureDetails(result) {
+      printNewline();
+      print(result.fullName);
+
+      for (var i = 0; i < result.failedExpectations.length; i++) {
+        var failedExpectation = result.failedExpectations[i];
+        printNewline();
+        print(indent(failedExpectation.stack, 2));
+      }
+
+      printNewline();
+    }
+  }
+
+  return ConsoleReporter;
+};
diff --git a/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/jasmine-html.js b/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/jasmine-html.js
new file mode 100755
index 0000000000000000000000000000000000000000..985d0d1a0a358239433a4aabf87e5cd43406453f
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/jasmine-html.js
@@ -0,0 +1,359 @@
+/*
+Copyright (c) 2008-2013 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+jasmineRequire.html = function(j$) {
+  j$.ResultsNode = jasmineRequire.ResultsNode();
+  j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
+  j$.QueryString = jasmineRequire.QueryString();
+  j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter();
+};
+
+jasmineRequire.HtmlReporter = function(j$) {
+
+  var noopTimer = {
+    start: function() {},
+    elapsed: function() { return 0; }
+  };
+
+  function HtmlReporter(options) {
+    var env = options.env || {},
+      getContainer = options.getContainer,
+      createElement = options.createElement,
+      createTextNode = options.createTextNode,
+      onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {},
+      timer = options.timer || noopTimer,
+      results = [],
+      specsExecuted = 0,
+      failureCount = 0,
+      pendingSpecCount = 0,
+      htmlReporterMain,
+      symbols;
+
+    this.initialize = function() {
+      htmlReporterMain = createDom("div", {className: "html-reporter"},
+        createDom("div", {className: "banner"},
+          createDom("span", {className: "title"}, "Jasmine"),
+          createDom("span", {className: "version"}, j$.version)
+        ),
+        createDom("ul", {className: "symbol-summary"}),
+        createDom("div", {className: "alert"}),
+        createDom("div", {className: "results"},
+          createDom("div", {className: "failures"})
+        )
+      );
+      getContainer().appendChild(htmlReporterMain);
+
+      symbols = find(".symbol-summary");
+    };
+
+    var totalSpecsDefined;
+    this.jasmineStarted = function(options) {
+      totalSpecsDefined = options.totalSpecsDefined || 0;
+      timer.start();
+    };
+
+    var summary = createDom("div", {className: "summary"});
+
+    var topResults = new j$.ResultsNode({}, "", null),
+      currentParent = topResults;
+
+    this.suiteStarted = function(result) {
+      currentParent.addChild(result, "suite");
+      currentParent = currentParent.last();
+    };
+
+    this.suiteDone = function(result) {
+      if (currentParent == topResults) {
+        return;
+      }
+
+      currentParent = currentParent.parent;
+    };
+
+    this.specStarted = function(result) {
+      currentParent.addChild(result, "spec");
+    };
+
+    var failures = [];
+    this.specDone = function(result) {
+      if (result.status != "disabled") {
+        specsExecuted++;
+      }
+
+      symbols.appendChild(createDom("li", {
+          className: result.status,
+          id: "spec_" + result.id,
+          title: result.fullName
+        }
+      ));
+
+      if (result.status == "failed") {
+        failureCount++;
+
+        var failure =
+          createDom("div", {className: "spec-detail failed"},
+            createDom("div", {className: "description"},
+              createDom("a", {title: result.fullName, href: specHref(result)}, result.fullName)
+            ),
+            createDom("div", {className: "messages"})
+          );
+        var messages = failure.childNodes[1];
+
+        for (var i = 0; i < result.failedExpectations.length; i++) {
+          var expectation = result.failedExpectations[i];
+          messages.appendChild(createDom("div", {className: "result-message"}, expectation.message));
+          messages.appendChild(createDom("div", {className: "stack-trace"}, expectation.stack));
+        }
+
+        failures.push(failure);
+      }
+
+      if (result.status == "pending") {
+        pendingSpecCount++;
+      }
+    };
+
+    this.jasmineDone = function() {
+      var banner = find(".banner");
+      banner.appendChild(createDom("span", {className: "duration"}, "finished in " + timer.elapsed() / 1000 + "s"));
+
+      var alert = find(".alert");
+
+      alert.appendChild(createDom("span", { className: "exceptions" },
+        createDom("label", { className: "label", 'for': "raise-exceptions" }, "raise exceptions"),
+        createDom("input", {
+          className: "raise",
+          id: "raise-exceptions",
+          type: "checkbox"
+        })
+      ));
+      var checkbox = find("input");
+
+      checkbox.checked = !env.catchingExceptions();
+      checkbox.onclick = onRaiseExceptionsClick;
+
+      if (specsExecuted < totalSpecsDefined) {
+        var skippedMessage = "Ran " + specsExecuted + " of " + totalSpecsDefined + " specs - run all";
+        alert.appendChild(
+          createDom("span", {className: "bar skipped"},
+            createDom("a", {href: "?", title: "Run all specs"}, skippedMessage)
+          )
+        );
+      }
+      var statusBarMessage = "" + pluralize("spec", specsExecuted) + ", " + pluralize("failure", failureCount);
+      if (pendingSpecCount) { statusBarMessage += ", " + pluralize("pending spec", pendingSpecCount); }
+
+      var statusBarClassName = "bar " + ((failureCount > 0) ? "failed" : "passed");
+      alert.appendChild(createDom("span", {className: statusBarClassName}, statusBarMessage));
+
+      var results = find(".results");
+      results.appendChild(summary);
+
+      summaryList(topResults, summary);
+
+      function summaryList(resultsTree, domParent) {
+        var specListNode;
+        for (var i = 0; i < resultsTree.children.length; i++) {
+          var resultNode = resultsTree.children[i];
+          if (resultNode.type == "suite") {
+            var suiteListNode = createDom("ul", {className: "suite", id: "suite-" + resultNode.result.id},
+              createDom("li", {className: "suite-detail"},
+                createDom("a", {href: specHref(resultNode.result)}, resultNode.result.description)
+              )
+            );
+
+            summaryList(resultNode, suiteListNode);
+            domParent.appendChild(suiteListNode);
+          }
+          if (resultNode.type == "spec") {
+            if (domParent.getAttribute("class") != "specs") {
+              specListNode = createDom("ul", {className: "specs"});
+              domParent.appendChild(specListNode);
+            }
+            specListNode.appendChild(
+              createDom("li", {
+                  className: resultNode.result.status,
+                  id: "spec-" + resultNode.result.id
+                },
+                createDom("a", {href: specHref(resultNode.result)}, resultNode.result.description)
+              )
+            );
+          }
+        }
+      }
+
+      if (failures.length) {
+        alert.appendChild(
+          createDom('span', {className: "menu bar spec-list"},
+            createDom("span", {}, "Spec List | "),
+            createDom('a', {className: "failures-menu", href: "#"}, "Failures")));
+        alert.appendChild(
+          createDom('span', {className: "menu bar failure-list"},
+            createDom('a', {className: "spec-list-menu", href: "#"}, "Spec List"),
+            createDom("span", {}, " | Failures ")));
+
+        find(".failures-menu").onclick = function() {
+          setMenuModeTo('failure-list');
+        };
+        find(".spec-list-menu").onclick = function() {
+          setMenuModeTo('spec-list');
+        };
+
+        setMenuModeTo('failure-list');
+
+        var failureNode = find(".failures");
+        for (var i = 0; i < failures.length; i++) {
+          failureNode.appendChild(failures[i]);
+        }
+      }
+    };
+
+    return this;
+
+    function find(selector) {
+      return getContainer().querySelector(selector);
+    }
+
+    function createDom(type, attrs, childrenVarArgs) {
+      var el = createElement(type);
+
+      for (var i = 2; i < arguments.length; i++) {
+        var child = arguments[i];
+
+        if (typeof child === 'string') {
+          el.appendChild(createTextNode(child));
+        } else {
+          if (child) {
+            el.appendChild(child);
+          }
+        }
+      }
+
+      for (var attr in attrs) {
+        if (attr == "className") {
+          el[attr] = attrs[attr];
+        } else {
+          el.setAttribute(attr, attrs[attr]);
+        }
+      }
+
+      return el;
+    }
+
+    function pluralize(singular, count) {
+      var word = (count == 1 ? singular : singular + "s");
+
+      return "" + count + " " + word;
+    }
+
+    function specHref(result) {
+      return "?spec=" + encodeURIComponent(result.fullName);
+    }
+
+    function setMenuModeTo(mode) {
+      htmlReporterMain.setAttribute("class", "html-reporter " + mode);
+    }
+  }
+
+  return HtmlReporter;
+};
+
+jasmineRequire.HtmlSpecFilter = function() {
+  function HtmlSpecFilter(options) {
+    var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
+    var filterPattern = new RegExp(filterString);
+
+    this.matches = function(specName) {
+      return filterPattern.test(specName);
+    };
+  }
+
+  return HtmlSpecFilter;
+};
+
+jasmineRequire.ResultsNode = function() {
+  function ResultsNode(result, type, parent) {
+    this.result = result;
+    this.type = type;
+    this.parent = parent;
+
+    this.children = [];
+
+    this.addChild = function(result, type) {
+      this.children.push(new ResultsNode(result, type, this));
+    };
+
+    this.last = function() {
+      return this.children[this.children.length - 1];
+    };
+  }
+
+  return ResultsNode;
+};
+
+jasmineRequire.QueryString = function() {
+  function QueryString(options) {
+
+    this.setParam = function(key, value) {
+      var paramMap = queryStringToParamMap();
+      paramMap[key] = value;
+      options.getWindowLocation().search = toQueryString(paramMap);
+    };
+
+    this.getParam = function(key) {
+      return queryStringToParamMap()[key];
+    };
+
+    return this;
+
+    function toQueryString(paramMap) {
+      var qStrPairs = [];
+      for (var prop in paramMap) {
+        qStrPairs.push(encodeURIComponent(prop) + "=" + encodeURIComponent(paramMap[prop]));
+      }
+      return "?" + qStrPairs.join('&');
+    }
+
+    function queryStringToParamMap() {
+      var paramStr = options.getWindowLocation().search.substring(1),
+        params = [],
+        paramMap = {};
+
+      if (paramStr.length > 0) {
+        params = paramStr.split('&');
+        for (var i = 0; i < params.length; i++) {
+          var p = params[i].split('=');
+          var value = decodeURIComponent(p[1]);
+          if (value === "true" || value === "false") {
+            value = JSON.parse(value);
+          }
+          paramMap[decodeURIComponent(p[0])] = value;
+        }
+      }
+
+      return paramMap;
+    }
+
+  }
+
+  return QueryString;
+};
diff --git a/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/jasmine.css b/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/jasmine.css
new file mode 100755
index 0000000000000000000000000000000000000000..f4d35b6e788d50254d5683c09558671c7ca2e343
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/jasmine.css
@@ -0,0 +1,55 @@
+body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
+
+.html-reporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
+.html-reporter a { text-decoration: none; }
+.html-reporter a:hover { text-decoration: underline; }
+.html-reporter p, .html-reporter h1, .html-reporter h2, .html-reporter h3, .html-reporter h4, .html-reporter h5, .html-reporter h6 { margin: 0; line-height: 14px; }
+.html-reporter .banner, .html-reporter .symbol-summary, .html-reporter .summary, .html-reporter .result-message, .html-reporter .spec .description, .html-reporter .spec-detail .description, .html-reporter .alert .bar, .html-reporter .stack-trace { padding-left: 9px; padding-right: 9px; }
+.html-reporter .banner .version { margin-left: 14px; }
+.html-reporter #jasmine_content { position: fixed; right: 100%; }
+.html-reporter .version { color: #aaaaaa; }
+.html-reporter .banner { margin-top: 14px; }
+.html-reporter .duration { color: #aaaaaa; float: right; }
+.html-reporter .symbol-summary { overflow: hidden; *zoom: 1; margin: 14px 0; }
+.html-reporter .symbol-summary li { display: inline-block; height: 8px; width: 14px; font-size: 16px; }
+.html-reporter .symbol-summary li.passed { font-size: 14px; }
+.html-reporter .symbol-summary li.passed:before { color: #5e7d00; content: "\02022"; }
+.html-reporter .symbol-summary li.failed { line-height: 9px; }
+.html-reporter .symbol-summary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
+.html-reporter .symbol-summary li.disabled { font-size: 14px; }
+.html-reporter .symbol-summary li.disabled:before { color: #bababa; content: "\02022"; }
+.html-reporter .symbol-summary li.pending { line-height: 17px; }
+.html-reporter .symbol-summary li.pending:before { color: #ba9d37; content: "*"; }
+.html-reporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; }
+.html-reporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
+.html-reporter .bar.failed { background-color: #b03911; }
+.html-reporter .bar.passed { background-color: #a6b779; }
+.html-reporter .bar.skipped { background-color: #bababa; }
+.html-reporter .bar.menu { background-color: #fff; color: #aaaaaa; }
+.html-reporter .bar.menu a { color: #333333; }
+.html-reporter .bar a { color: white; }
+.html-reporter.spec-list .bar.menu.failure-list, .html-reporter.spec-list .results .failures { display: none; }
+.html-reporter.failure-list .bar.menu.spec-list, .html-reporter.failure-list .summary { display: none; }
+.html-reporter .running-alert { background-color: #666666; }
+.html-reporter .results { margin-top: 14px; }
+.html-reporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
+.html-reporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
+.html-reporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
+.html-reporter.showDetails .summary { display: none; }
+.html-reporter.showDetails #details { display: block; }
+.html-reporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
+.html-reporter .summary { margin-top: 14px; }
+.html-reporter .summary ul { list-style-type: none; margin-left: 14px; padding-top: 0; padding-left: 0; }
+.html-reporter .summary ul.suite { margin-top: 7px; margin-bottom: 7px; }
+.html-reporter .summary li.passed a { color: #5e7d00; }
+.html-reporter .summary li.failed a { color: #b03911; }
+.html-reporter .summary li.pending a { color: #ba9d37; }
+.html-reporter .description + .suite { margin-top: 0; }
+.html-reporter .suite { margin-top: 14px; }
+.html-reporter .suite a { color: #333333; }
+.html-reporter .failures .spec-detail { margin-bottom: 28px; }
+.html-reporter .failures .spec-detail .description { background-color: #b03911; }
+.html-reporter .failures .spec-detail .description a { color: white; }
+.html-reporter .result-message { padding-top: 14px; color: #333333; white-space: pre; }
+.html-reporter .result-message span.result { display: block; }
+.html-reporter .stack-trace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
diff --git a/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/jasmine.js b/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/jasmine.js
new file mode 100755
index 0000000000000000000000000000000000000000..24463ecb83b248db47169ba79822353eab4852f4
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/jasmine.js
@@ -0,0 +1,2402 @@
+/*
+Copyright (c) 2008-2013 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+function getJasmineRequireObj() {
+  if (typeof module !== "undefined" && module.exports) {
+    return exports;
+  } else {
+    window.jasmineRequire = window.jasmineRequire || {};
+    return window.jasmineRequire;
+  }
+}
+
+getJasmineRequireObj().core = function(jRequire) {
+  var j$ = {};
+
+  jRequire.base(j$);
+  j$.util = jRequire.util();
+  j$.Any = jRequire.Any();
+  j$.CallTracker = jRequire.CallTracker();
+  j$.Clock = jRequire.Clock();
+  j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler();
+  j$.Env = jRequire.Env(j$);
+  j$.ExceptionFormatter = jRequire.ExceptionFormatter();
+  j$.Expectation = jRequire.Expectation();
+  j$.buildExpectationResult = jRequire.buildExpectationResult();
+  j$.JsApiReporter = jRequire.JsApiReporter();
+  j$.matchersUtil = jRequire.matchersUtil(j$);
+  j$.ObjectContaining = jRequire.ObjectContaining(j$);
+  j$.pp = jRequire.pp(j$);
+  j$.QueueRunner = jRequire.QueueRunner();
+  j$.ReportDispatcher = jRequire.ReportDispatcher();
+  j$.Spec = jRequire.Spec(j$);
+  j$.SpyStrategy = jRequire.SpyStrategy();
+  j$.Suite = jRequire.Suite();
+  j$.Timer = jRequire.Timer();
+  j$.version = jRequire.version();
+
+  j$.matchers = jRequire.requireMatchers(jRequire, j$);
+
+  return j$;
+};
+
+getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
+  var availableMatchers = [
+      "toBe",
+      "toBeCloseTo",
+      "toBeDefined",
+      "toBeFalsy",
+      "toBeGreaterThan",
+      "toBeLessThan",
+      "toBeNaN",
+      "toBeNull",
+      "toBeTruthy",
+      "toBeUndefined",
+      "toContain",
+      "toEqual",
+      "toHaveBeenCalled",
+      "toHaveBeenCalledWith",
+      "toMatch",
+      "toThrow",
+      "toThrowError"
+    ],
+    matchers = {};
+
+  for (var i = 0; i < availableMatchers.length; i++) {
+    var name = availableMatchers[i];
+    matchers[name] = jRequire[name](j$);
+  }
+
+  return matchers;
+};
+
+getJasmineRequireObj().base = function(j$) {
+  j$.unimplementedMethod_ = function() {
+    throw new Error("unimplemented method");
+  };
+
+  j$.MAX_PRETTY_PRINT_DEPTH = 40;
+  j$.DEFAULT_TIMEOUT_INTERVAL = 5000;
+
+  j$.getGlobal = (function() {
+    var jasmineGlobal = eval.call(null, "this");
+    return function() {
+      return jasmineGlobal;
+    };
+  })();
+
+  j$.getEnv = function(options) {
+    var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options);
+    //jasmine. singletons in here (setTimeout blah blah).
+    return env;
+  };
+
+  j$.isArray_ = function(value) {
+    return j$.isA_("Array", value);
+  };
+
+  j$.isString_ = function(value) {
+    return j$.isA_("String", value);
+  };
+
+  j$.isNumber_ = function(value) {
+    return j$.isA_("Number", value);
+  };
+
+  j$.isA_ = function(typeName, value) {
+    return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
+  };
+
+  j$.isDomNode = function(obj) {
+    return obj.nodeType > 0;
+  };
+
+  j$.any = function(clazz) {
+    return new j$.Any(clazz);
+  };
+
+  j$.objectContaining = function(sample) {
+    return new j$.ObjectContaining(sample);
+  };
+
+  j$.createSpy = function(name, originalFn) {
+
+    var spyStrategy = new j$.SpyStrategy({
+        name: name,
+        fn: originalFn,
+        getSpy: function() { return spy; }
+      }),
+      callTracker = new j$.CallTracker(),
+      spy = function() {
+        callTracker.track({
+          object: this,
+          args: Array.prototype.slice.apply(arguments)
+        });
+        return spyStrategy.exec.apply(this, arguments);
+      };
+
+    for (var prop in originalFn) {
+      if (prop === 'and' || prop === 'calls') {
+        throw new Error("Jasmine spies would overwrite the 'and' and 'calls' properties on the object being spied upon");
+      }
+
+      spy[prop] = originalFn[prop];
+    }
+
+    spy.and = spyStrategy;
+    spy.calls = callTracker;
+
+    return spy;
+  };
+
+  j$.isSpy = function(putativeSpy) {
+    if (!putativeSpy) {
+      return false;
+    }
+    return putativeSpy.and instanceof j$.SpyStrategy &&
+      putativeSpy.calls instanceof j$.CallTracker;
+  };
+
+  j$.createSpyObj = function(baseName, methodNames) {
+    if (!j$.isArray_(methodNames) || methodNames.length === 0) {
+      throw "createSpyObj requires a non-empty array of method names to create spies for";
+    }
+    var obj = {};
+    for (var i = 0; i < methodNames.length; i++) {
+      obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]);
+    }
+    return obj;
+  };
+};
+
+getJasmineRequireObj().util = function() {
+
+  var util = {};
+
+  util.inherit = function(childClass, parentClass) {
+    var Subclass = function() {
+    };
+    Subclass.prototype = parentClass.prototype;
+    childClass.prototype = new Subclass();
+  };
+
+  util.htmlEscape = function(str) {
+    if (!str) {
+      return str;
+    }
+    return str.replace(/&/g, '&amp;')
+      .replace(/</g, '&lt;')
+      .replace(/>/g, '&gt;');
+  };
+
+  util.argsToArray = function(args) {
+    var arrayOfArgs = [];
+    for (var i = 0; i < args.length; i++) {
+      arrayOfArgs.push(args[i]);
+    }
+    return arrayOfArgs;
+  };
+
+  util.isUndefined = function(obj) {
+    return obj === void 0;
+  };
+
+  return util;
+};
+
+getJasmineRequireObj().Spec = function(j$) {
+  function Spec(attrs) {
+    this.expectationFactory = attrs.expectationFactory;
+    this.resultCallback = attrs.resultCallback || function() {};
+    this.id = attrs.id;
+    this.description = attrs.description || '';
+    this.fn = attrs.fn;
+    this.beforeFns = attrs.beforeFns || function() { return []; };
+    this.afterFns = attrs.afterFns || function() { return []; };
+    this.onStart = attrs.onStart || function() {};
+    this.exceptionFormatter = attrs.exceptionFormatter || function() {};
+    this.getSpecName = attrs.getSpecName || function() { return ''; };
+    this.expectationResultFactory = attrs.expectationResultFactory || function() { };
+    this.queueRunnerFactory = attrs.queueRunnerFactory || function() {};
+    this.catchingExceptions = attrs.catchingExceptions || function() { return true; };
+
+    this.timer = attrs.timer || {setTimeout: setTimeout, clearTimeout: clearTimeout};
+
+    if (!this.fn) {
+      this.pend();
+    }
+
+    this.result = {
+      id: this.id,
+      description: this.description,
+      fullName: this.getFullName(),
+      failedExpectations: []
+    };
+  }
+
+  Spec.prototype.addExpectationResult = function(passed, data) {
+    if (passed) {
+      return;
+    }
+    this.result.failedExpectations.push(this.expectationResultFactory(data));
+  };
+
+  Spec.prototype.expect = function(actual) {
+    return this.expectationFactory(actual, this);
+  };
+
+  Spec.prototype.execute = function(onComplete) {
+    var self = this,
+        timeout;
+
+    this.onStart(this);
+
+    if (this.markedPending || this.disabled) {
+      complete();
+      return;
+    }
+
+    function timeoutable(fn) {
+      return function(done) {
+        timeout = Function.prototype.apply.apply(self.timer.setTimeout, [j$.getGlobal(), [function() {
+          onException(new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'));
+          done();
+        }, j$.DEFAULT_TIMEOUT_INTERVAL]]);
+
+        var callDone = function() {
+          clearTimeoutable();
+          done();
+        };
+
+        fn.call(this, callDone); //TODO: do we care about more than 1 arg?
+      };
+    }
+
+    function clearTimeoutable() {
+      Function.prototype.apply.apply(self.timer.clearTimeout, [j$.getGlobal(), [timeout]]);
+      timeout = void 0;
+    }
+
+    var allFns = this.beforeFns().concat(this.fn).concat(this.afterFns()),
+      allTimeoutableFns = [];
+    for (var i = 0; i < allFns.length; i++) {
+      var fn = allFns[i];
+      allTimeoutableFns.push(fn.length > 0 ? timeoutable(fn) : fn);
+    }
+
+    this.queueRunnerFactory({
+      fns: allTimeoutableFns,
+      onException: onException,
+      onComplete: complete
+    });
+
+    function onException(e) {
+      clearTimeoutable();
+      if (Spec.isPendingSpecException(e)) {
+        self.pend();
+        return;
+      }
+
+      self.addExpectationResult(false, {
+        matcherName: "",
+        passed: false,
+        expected: "",
+        actual: "",
+        error: e
+      });
+    }
+
+    function complete() {
+      self.result.status = self.status();
+      self.resultCallback(self.result);
+
+      if (onComplete) {
+        onComplete();
+      }
+    }
+  };
+
+  Spec.prototype.disable = function() {
+    this.disabled = true;
+  };
+
+  Spec.prototype.pend = function() {
+    this.markedPending = true;
+  };
+
+  Spec.prototype.status = function() {
+    if (this.disabled) {
+      return 'disabled';
+    }
+
+    if (this.markedPending) {
+      return 'pending';
+    }
+
+    if (this.result.failedExpectations.length > 0) {
+      return 'failed';
+    } else {
+      return 'passed';
+    }
+  };
+
+  Spec.prototype.getFullName = function() {
+    return this.getSpecName(this);
+  };
+
+  Spec.pendingSpecExceptionMessage = "=> marked Pending";
+
+  Spec.isPendingSpecException = function(e) {
+    return e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1;
+  };
+
+  return Spec;
+};
+
+if (typeof window == void 0 && typeof exports == "object") {
+  exports.Spec = jasmineRequire.Spec;
+}
+
+getJasmineRequireObj().Env = function(j$) {
+  function Env(options) {
+    options = options || {};
+
+    var self = this;
+    var global = options.global || j$.getGlobal();
+
+    var totalSpecsDefined = 0;
+
+    var catchExceptions = true;
+
+    var realSetTimeout = j$.getGlobal().setTimeout;
+    var realClearTimeout = j$.getGlobal().clearTimeout;
+    this.clock = new j$.Clock(global, new j$.DelayedFunctionScheduler());
+
+    var runnableLookupTable = {};
+
+    var spies = [];
+
+    var currentSpec = null;
+    var currentSuite = null;
+
+    var reporter = new j$.ReportDispatcher([
+      "jasmineStarted",
+      "jasmineDone",
+      "suiteStarted",
+      "suiteDone",
+      "specStarted",
+      "specDone"
+    ]);
+
+    this.specFilter = function() {
+      return true;
+    };
+
+    var equalityTesters = [];
+
+    var customEqualityTesters = [];
+    this.addCustomEqualityTester = function(tester) {
+      customEqualityTesters.push(tester);
+    };
+
+    j$.Expectation.addCoreMatchers(j$.matchers);
+
+    var nextSpecId = 0;
+    var getNextSpecId = function() {
+      return 'spec' + nextSpecId++;
+    };
+
+    var nextSuiteId = 0;
+    var getNextSuiteId = function() {
+      return 'suite' + nextSuiteId++;
+    };
+
+    var expectationFactory = function(actual, spec) {
+      return j$.Expectation.Factory({
+        util: j$.matchersUtil,
+        customEqualityTesters: customEqualityTesters,
+        actual: actual,
+        addExpectationResult: addExpectationResult
+      });
+
+      function addExpectationResult(passed, result) {
+        return spec.addExpectationResult(passed, result);
+      }
+    };
+
+    var specStarted = function(spec) {
+      currentSpec = spec;
+      reporter.specStarted(spec.result);
+    };
+
+    var beforeFns = function(suite) {
+      return function() {
+        var befores = [];
+        while(suite) {
+          befores = befores.concat(suite.beforeFns);
+          suite = suite.parentSuite;
+        }
+        return befores.reverse();
+      };
+    };
+
+    var afterFns = function(suite) {
+      return function() {
+        var afters = [];
+        while(suite) {
+          afters = afters.concat(suite.afterFns);
+          suite = suite.parentSuite;
+        }
+        return afters;
+      };
+    };
+
+    var getSpecName = function(spec, suite) {
+      return suite.getFullName() + ' ' + spec.description;
+    };
+
+    // TODO: we may just be able to pass in the fn instead of wrapping here
+    var buildExpectationResult = j$.buildExpectationResult,
+        exceptionFormatter = new j$.ExceptionFormatter(),
+        expectationResultFactory = function(attrs) {
+          attrs.messageFormatter = exceptionFormatter.message;
+          attrs.stackFormatter = exceptionFormatter.stack;
+
+          return buildExpectationResult(attrs);
+        };
+
+    // TODO: fix this naming, and here's where the value comes in
+    this.catchExceptions = function(value) {
+      catchExceptions = !!value;
+      return catchExceptions;
+    };
+
+    this.catchingExceptions = function() {
+      return catchExceptions;
+    };
+
+    var maximumSpecCallbackDepth = 20;
+    var currentSpecCallbackDepth = 0;
+
+    function clearStack(fn) {
+      currentSpecCallbackDepth++;
+      if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) {
+        currentSpecCallbackDepth = 0;
+        realSetTimeout(fn, 0);
+      } else {
+        fn();
+      }
+    }
+
+    var catchException = function(e) {
+      return j$.Spec.isPendingSpecException(e) || catchExceptions;
+    };
+
+    var queueRunnerFactory = function(options) {
+      options.catchException = catchException;
+      options.clearStack = options.clearStack || clearStack;
+
+      new j$.QueueRunner(options).execute();
+    };
+
+    var topSuite = new j$.Suite({
+      env: this,
+      id: getNextSuiteId(),
+      description: 'Jasmine__TopLevel__Suite',
+      queueRunner: queueRunnerFactory,
+      resultCallback: function() {} // TODO - hook this up
+    });
+    runnableLookupTable[topSuite.id] = topSuite;
+    currentSuite = topSuite;
+
+    this.topSuite = function() {
+      return topSuite;
+    };
+
+    this.execute = function(runnablesToRun) {
+      runnablesToRun = runnablesToRun || [topSuite.id];
+
+      var allFns = [];
+      for(var i = 0; i < runnablesToRun.length; i++) {
+        var runnable = runnableLookupTable[runnablesToRun[i]];
+        allFns.push((function(runnable) { return function(done) { runnable.execute(done); }; })(runnable));
+      }
+
+      reporter.jasmineStarted({
+        totalSpecsDefined: totalSpecsDefined
+      });
+
+      queueRunnerFactory({fns: allFns, onComplete: reporter.jasmineDone});
+    };
+
+    this.addReporter = function(reporterToAdd) {
+      reporter.addReporter(reporterToAdd);
+    };
+
+    this.addMatchers = function(matchersToAdd) {
+      j$.Expectation.addMatchers(matchersToAdd);
+    };
+
+    this.spyOn = function(obj, methodName) {
+      if (j$.util.isUndefined(obj)) {
+        throw new Error("spyOn could not find an object to spy upon for " + methodName + "()");
+      }
+
+      if (j$.util.isUndefined(obj[methodName])) {
+        throw new Error(methodName + '() method does not exist');
+      }
+
+      if (obj[methodName] && j$.isSpy(obj[methodName])) {
+        //TODO?: should this return the current spy? Downside: may cause user confusion about spy state
+        throw new Error(methodName + ' has already been spied upon');
+      }
+
+      var spy = j$.createSpy(methodName, obj[methodName]);
+
+      spies.push({
+        spy: spy,
+        baseObj: obj,
+        methodName: methodName,
+        originalValue: obj[methodName]
+      });
+
+      obj[methodName] = spy;
+
+      return spy;
+    };
+
+    var suiteFactory = function(description) {
+      var suite = new j$.Suite({
+        env: self,
+        id: getNextSuiteId(),
+        description: description,
+        parentSuite: currentSuite,
+        queueRunner: queueRunnerFactory,
+        onStart: suiteStarted,
+        resultCallback: function(attrs) {
+          reporter.suiteDone(attrs);
+        }
+      });
+
+      runnableLookupTable[suite.id] = suite;
+      return suite;
+    };
+
+    this.describe = function(description, specDefinitions) {
+      var suite = suiteFactory(description);
+
+      var parentSuite = currentSuite;
+      parentSuite.addChild(suite);
+      currentSuite = suite;
+
+      var declarationError = null;
+      try {
+        specDefinitions.call(suite);
+      } catch (e) {
+        declarationError = e;
+      }
+
+      if (declarationError) {
+        this.it("encountered a declaration exception", function() {
+          throw declarationError;
+        });
+      }
+
+      currentSuite = parentSuite;
+
+      return suite;
+    };
+
+    this.xdescribe = function(description, specDefinitions) {
+      var suite = this.describe(description, specDefinitions);
+      suite.disable();
+      return suite;
+    };
+
+    var specFactory = function(description, fn, suite) {
+      totalSpecsDefined++;
+
+      var spec = new j$.Spec({
+        id: getNextSpecId(),
+        beforeFns: beforeFns(suite),
+        afterFns: afterFns(suite),
+        expectationFactory: expectationFactory,
+        exceptionFormatter: exceptionFormatter,
+        resultCallback: specResultCallback,
+        getSpecName: function(spec) {
+          return getSpecName(spec, suite);
+        },
+        onStart: specStarted,
+        description: description,
+        expectationResultFactory: expectationResultFactory,
+        queueRunnerFactory: queueRunnerFactory,
+        fn: fn,
+        timer: {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}
+      });
+
+      runnableLookupTable[spec.id] = spec;
+
+      if (!self.specFilter(spec)) {
+        spec.disable();
+      }
+
+      return spec;
+
+      function removeAllSpies() {
+        for (var i = 0; i < spies.length; i++) {
+          var spyEntry = spies[i];
+          spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue;
+        }
+        spies = [];
+      }
+
+      function specResultCallback(result) {
+        removeAllSpies();
+        j$.Expectation.resetMatchers();
+        customEqualityTesters = [];
+        currentSpec = null;
+        reporter.specDone(result);
+      }
+    };
+
+    var suiteStarted = function(suite) {
+      reporter.suiteStarted(suite.result);
+    };
+
+    this.it = function(description, fn) {
+      var spec = specFactory(description, fn, currentSuite);
+      currentSuite.addChild(spec);
+      return spec;
+    };
+
+    this.xit = function(description, fn) {
+      var spec = this.it(description, fn);
+      spec.pend();
+      return spec;
+    };
+
+    this.expect = function(actual) {
+      return currentSpec.expect(actual);
+    };
+
+    this.beforeEach = function(beforeEachFunction) {
+      currentSuite.beforeEach(beforeEachFunction);
+    };
+
+    this.afterEach = function(afterEachFunction) {
+      currentSuite.afterEach(afterEachFunction);
+    };
+
+    this.pending = function() {
+      throw j$.Spec.pendingSpecExceptionMessage;
+    };
+  }
+
+  return Env;
+};
+
+getJasmineRequireObj().JsApiReporter = function() {
+
+  var noopTimer = {
+    start: function(){},
+    elapsed: function(){ return 0; }
+  };
+
+  function JsApiReporter(options) {
+    var timer = options.timer || noopTimer,
+        status = "loaded";
+
+    this.started = false;
+    this.finished = false;
+
+    this.jasmineStarted = function() {
+      this.started = true;
+      status = 'started';
+      timer.start();
+    };
+
+    var executionTime;
+
+    this.jasmineDone = function() {
+      this.finished = true;
+      executionTime = timer.elapsed();
+      status = 'done';
+    };
+
+    this.status = function() {
+      return status;
+    };
+
+    var suites = {};
+
+    this.suiteStarted = function(result) {
+      storeSuite(result);
+    };
+
+    this.suiteDone = function(result) {
+      storeSuite(result);
+    };
+
+    function storeSuite(result) {
+      suites[result.id] = result;
+    }
+
+    this.suites = function() {
+      return suites;
+    };
+
+    var specs = [];
+    this.specStarted = function(result) { };
+
+    this.specDone = function(result) {
+      specs.push(result);
+    };
+
+    this.specResults = function(index, length) {
+      return specs.slice(index, index + length);
+    };
+
+    this.specs = function() {
+      return specs;
+    };
+
+    this.executionTime = function() {
+      return executionTime;
+    };
+
+  }
+
+  return JsApiReporter;
+};
+
+getJasmineRequireObj().Any = function() {
+
+  function Any(expectedObject) {
+    this.expectedObject = expectedObject;
+  }
+
+  Any.prototype.jasmineMatches = function(other) {
+    if (this.expectedObject == String) {
+      return typeof other == 'string' || other instanceof String;
+    }
+
+    if (this.expectedObject == Number) {
+      return typeof other == 'number' || other instanceof Number;
+    }
+
+    if (this.expectedObject == Function) {
+      return typeof other == 'function' || other instanceof Function;
+    }
+
+    if (this.expectedObject == Object) {
+      return typeof other == 'object';
+    }
+    
+    if (this.expectedObject == Boolean) {
+      return typeof other == 'boolean';
+    }
+
+    return other instanceof this.expectedObject;
+  };
+
+  Any.prototype.jasmineToString = function() {
+    return '<jasmine.any(' + this.expectedClass + ')>';
+  };
+
+  return Any;
+};
+
+getJasmineRequireObj().CallTracker = function() {
+
+  function CallTracker() {
+    var calls = [];
+
+    this.track = function(context) {
+      calls.push(context);
+    };
+
+    this.any = function() {
+      return !!calls.length;
+    };
+
+    this.count = function() {
+      return calls.length;
+    };
+
+    this.argsFor = function(index) {
+      var call = calls[index];
+      return call ? call.args : [];
+    };
+
+    this.all = function() {
+      return calls;
+    };
+
+    this.allArgs = function() {
+      var callArgs = [];
+      for(var i = 0; i < calls.length; i++){
+        callArgs.push(calls[i].args);
+      }
+
+      return callArgs;
+    };
+
+    this.first = function() {
+      return calls[0];
+    };
+
+    this.mostRecent = function() {
+      return calls[calls.length - 1];
+    };
+
+    this.reset = function() {
+      calls = [];
+    };
+  }
+
+  return CallTracker;
+};
+
+getJasmineRequireObj().Clock = function() {
+  function Clock(global, delayedFunctionScheduler) {
+    var self = this,
+      realTimingFunctions = {
+        setTimeout: global.setTimeout,
+        clearTimeout: global.clearTimeout,
+        setInterval: global.setInterval,
+        clearInterval: global.clearInterval
+      },
+      fakeTimingFunctions = {
+        setTimeout: setTimeout,
+        clearTimeout: clearTimeout,
+        setInterval: setInterval,
+        clearInterval: clearInterval
+      },
+      installed = false,
+      timer;
+
+    self.install = function() {
+      replace(global, fakeTimingFunctions);
+      timer = fakeTimingFunctions;
+      installed = true;
+    };
+
+    self.uninstall = function() {
+      delayedFunctionScheduler.reset();
+      replace(global, realTimingFunctions);
+      timer = realTimingFunctions;
+      installed = false;
+    };
+
+    self.setTimeout = function(fn, delay, params) {
+      if (legacyIE()) {
+        if (arguments.length > 2) {
+          throw new Error("IE < 9 cannot support extra params to setTimeout without a polyfill");
+        }
+        return timer.setTimeout(fn, delay);
+      }
+      return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]);
+    };
+
+    self.setInterval = function(fn, delay, params) {
+      if (legacyIE()) {
+        if (arguments.length > 2) {
+          throw new Error("IE < 9 cannot support extra params to setInterval without a polyfill");
+        }
+        return timer.setInterval(fn, delay);
+      }
+      return Function.prototype.apply.apply(timer.setInterval, [global, arguments]);
+    };
+
+    self.clearTimeout = function(id) {
+      return Function.prototype.call.apply(timer.clearTimeout, [global, id]);
+    };
+
+    self.clearInterval = function(id) {
+      return Function.prototype.call.apply(timer.clearInterval, [global, id]);
+    };
+
+    self.tick = function(millis) {
+      if (installed) {
+        delayedFunctionScheduler.tick(millis);
+      } else {
+        throw new Error("Mock clock is not installed, use jasmine.clock().install()");
+      }
+    };
+
+    return self;
+
+    function legacyIE() {
+      //if these methods are polyfilled, apply will be present
+      return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply;
+    }
+
+    function replace(dest, source) {
+      for (var prop in source) {
+        dest[prop] = source[prop];
+      }
+    }
+
+    function setTimeout(fn, delay) {
+      return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2));
+    }
+
+    function clearTimeout(id) {
+      return delayedFunctionScheduler.removeFunctionWithId(id);
+    }
+
+    function setInterval(fn, interval) {
+      return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true);
+    }
+
+    function clearInterval(id) {
+      return delayedFunctionScheduler.removeFunctionWithId(id);
+    }
+
+    function argSlice(argsObj, n) {
+      return Array.prototype.slice.call(argsObj, 2);
+    }
+  }
+
+  return Clock;
+};
+
+getJasmineRequireObj().DelayedFunctionScheduler = function() {
+  function DelayedFunctionScheduler() {
+    var self = this;
+    var scheduledLookup = [];
+    var scheduledFunctions = {};
+    var currentTime = 0;
+    var delayedFnCount = 0;
+
+    self.tick = function(millis) {
+      millis = millis || 0;
+      var endTime = currentTime + millis;
+
+      runScheduledFunctions(endTime);
+      currentTime = endTime;
+    };
+
+    self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) {
+      var f;
+      if (typeof(funcToCall) === 'string') {
+        /* jshint evil: true */
+        f = function() { return eval(funcToCall); };
+        /* jshint evil: false */
+      } else {
+        f = funcToCall;
+      }
+
+      millis = millis || 0;
+      timeoutKey = timeoutKey || ++delayedFnCount;
+      runAtMillis = runAtMillis || (currentTime + millis);
+
+      var funcToSchedule = {
+        runAtMillis: runAtMillis,
+        funcToCall: f,
+        recurring: recurring,
+        params: params,
+        timeoutKey: timeoutKey,
+        millis: millis
+      };
+
+      if (runAtMillis in scheduledFunctions) {
+        scheduledFunctions[runAtMillis].push(funcToSchedule);
+      } else {
+        scheduledFunctions[runAtMillis] = [funcToSchedule];
+        scheduledLookup.push(runAtMillis);
+        scheduledLookup.sort(function (a, b) {
+          return a - b;
+        });
+      }
+
+      return timeoutKey;
+    };
+
+    self.removeFunctionWithId = function(timeoutKey) {
+      for (var runAtMillis in scheduledFunctions) {
+        var funcs = scheduledFunctions[runAtMillis];
+        var i = indexOfFirstToPass(funcs, function (func) {
+          return func.timeoutKey === timeoutKey;
+        });
+
+        if (i > -1) {
+          if (funcs.length === 1) {
+            delete scheduledFunctions[runAtMillis];
+            deleteFromLookup(runAtMillis);
+          } else {
+            funcs.splice(i, 1);
+          }
+
+          // intervals get rescheduled when executed, so there's never more
+          // than a single scheduled function with a given timeoutKey
+          break;
+        }
+      }
+    };
+
+    self.reset = function() {
+      currentTime = 0;
+      scheduledLookup = [];
+      scheduledFunctions = {};
+      delayedFnCount = 0;
+    };
+
+    return self;
+
+    function indexOfFirstToPass(array, testFn) {
+      var index = -1;
+
+      for (var i = 0; i < array.length; ++i) {
+        if (testFn(array[i])) {
+          index = i;
+          break;
+        }
+      }
+
+      return index;
+    }
+
+    function deleteFromLookup(key) {
+      var value = Number(key);
+      var i = indexOfFirstToPass(scheduledLookup, function (millis) {
+        return millis === value;
+      });
+
+      if (i > -1) {
+        scheduledLookup.splice(i, 1);
+      }
+    }
+
+    function reschedule(scheduledFn) {
+      self.scheduleFunction(scheduledFn.funcToCall,
+        scheduledFn.millis,
+        scheduledFn.params,
+        true,
+        scheduledFn.timeoutKey,
+        scheduledFn.runAtMillis + scheduledFn.millis);
+    }
+
+    function runScheduledFunctions(endTime) {
+      if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) {
+        return;
+      }
+
+      do {
+        currentTime = scheduledLookup.shift();
+
+        var funcsToRun = scheduledFunctions[currentTime];
+        delete scheduledFunctions[currentTime];
+
+        for (var i = 0; i < funcsToRun.length; ++i) {
+          var funcToRun = funcsToRun[i];
+          funcToRun.funcToCall.apply(null, funcToRun.params || []);
+
+          if (funcToRun.recurring) {
+            reschedule(funcToRun);
+          }
+        }
+      } while (scheduledLookup.length > 0 &&
+              // checking first if we're out of time prevents setTimeout(0)
+              // scheduled in a funcToRun from forcing an extra iteration
+                 currentTime !== endTime  &&
+                 scheduledLookup[0] <= endTime);
+    }
+  }
+
+  return DelayedFunctionScheduler;
+};
+
+getJasmineRequireObj().ExceptionFormatter = function() {
+  function ExceptionFormatter() {
+    this.message = function(error) {
+      var message = error.name +
+        ': ' +
+        error.message;
+
+      if (error.fileName || error.sourceURL) {
+        message += " in " + (error.fileName || error.sourceURL);
+      }
+
+      if (error.line || error.lineNumber) {
+        message += " (line " + (error.line || error.lineNumber) + ")";
+      }
+
+      return message;
+    };
+
+    this.stack = function(error) {
+      return error ? error.stack : null;
+    };
+  }
+
+  return ExceptionFormatter;
+};
+
+getJasmineRequireObj().Expectation = function() {
+
+  var matchers = {};
+
+  function Expectation(options) {
+    this.util = options.util || { buildFailureMessage: function() {} };
+    this.customEqualityTesters = options.customEqualityTesters || [];
+    this.actual = options.actual;
+    this.addExpectationResult = options.addExpectationResult || function(){};
+    this.isNot = options.isNot;
+
+    for (var matcherName in matchers) {
+      this[matcherName] = matchers[matcherName];
+    }
+  }
+
+  Expectation.prototype.wrapCompare = function(name, matcherFactory) {
+    return function() {
+      var args = Array.prototype.slice.call(arguments, 0),
+        expected = args.slice(0),
+        message = "";
+
+      args.unshift(this.actual);
+
+      var matcher = matcherFactory(this.util, this.customEqualityTesters),
+          matcherCompare = matcher.compare;
+
+      function defaultNegativeCompare() {
+        var result = matcher.compare.apply(null, args);
+        result.pass = !result.pass;
+        return result;
+      }
+
+      if (this.isNot) {
+        matcherCompare = matcher.negativeCompare || defaultNegativeCompare;
+      }
+
+      var result = matcherCompare.apply(null, args);
+
+      if (!result.pass) {
+        if (!result.message) {
+          args.unshift(this.isNot);
+          args.unshift(name);
+          message = this.util.buildFailureMessage.apply(null, args);
+        } else {
+          message = result.message;
+        }
+      }
+
+      if (expected.length == 1) {
+        expected = expected[0];
+      }
+
+      // TODO: how many of these params are needed?
+      this.addExpectationResult(
+        result.pass,
+        {
+          matcherName: name,
+          passed: result.pass,
+          message: message,
+          actual: this.actual,
+          expected: expected // TODO: this may need to be arrayified/sliced
+        }
+      );
+    };
+  };
+
+  Expectation.addCoreMatchers = function(matchers) {
+    var prototype = Expectation.prototype;
+    for (var matcherName in matchers) {
+      var matcher = matchers[matcherName];
+      prototype[matcherName] = prototype.wrapCompare(matcherName, matcher);
+    }
+  };
+
+  Expectation.addMatchers = function(matchersToAdd) {
+    for (var name in matchersToAdd) {
+      var matcher = matchersToAdd[name];
+      matchers[name] = Expectation.prototype.wrapCompare(name, matcher);
+    }
+  };
+
+  Expectation.resetMatchers = function() {
+    for (var name in matchers) {
+      delete matchers[name];
+    }
+  };
+
+  Expectation.Factory = function(options) {
+    options = options || {};
+
+    var expect = new Expectation(options);
+
+    // TODO: this would be nice as its own Object - NegativeExpectation
+    // TODO: copy instead of mutate options
+    options.isNot = true;
+    expect.not = new Expectation(options);
+
+    return expect;
+  };
+
+  return Expectation;
+};
+
+//TODO: expectation result may make more sense as a presentation of an expectation.
+getJasmineRequireObj().buildExpectationResult = function() {
+  function buildExpectationResult(options) {
+    var messageFormatter = options.messageFormatter || function() {},
+      stackFormatter = options.stackFormatter || function() {};
+
+    return {
+      matcherName: options.matcherName,
+      expected: options.expected,
+      actual: options.actual,
+      message: message(),
+      stack: stack(),
+      passed: options.passed
+    };
+
+    function message() {
+      if (options.passed) {
+        return "Passed.";
+      } else if (options.message) {
+        return options.message;
+      } else if (options.error) {
+        return messageFormatter(options.error);
+      }
+      return "";
+    }
+
+    function stack() {
+      if (options.passed) {
+        return "";
+      }
+
+      var error = options.error;
+      if (!error) {
+        try {
+          throw new Error(message());
+        } catch (e) {
+          error = e;
+        }
+      }
+      return stackFormatter(error);
+    }
+  }
+
+  return buildExpectationResult;
+};
+
+getJasmineRequireObj().ObjectContaining = function(j$) {
+
+  function ObjectContaining(sample) {
+    this.sample = sample;
+  }
+
+  ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) {
+    if (typeof(this.sample) !== "object") { throw new Error("You must provide an object to objectContaining, not '"+this.sample+"'."); }
+
+    mismatchKeys = mismatchKeys || [];
+    mismatchValues = mismatchValues || [];
+
+    var hasKey = function(obj, keyName) {
+      return obj !== null && !j$.util.isUndefined(obj[keyName]);
+    };
+
+    for (var property in this.sample) {
+      if (!hasKey(other, property) && hasKey(this.sample, property)) {
+        mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
+      }
+      else if (!j$.matchersUtil.equals(this.sample[property], other[property])) {
+        mismatchValues.push("'" + property + "' was '" + (other[property] ? j$.util.htmlEscape(other[property].toString()) : other[property]) + "' in actual, but was '" + (this.sample[property] ? j$.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in expected.");
+      }
+    }
+
+    return (mismatchKeys.length === 0 && mismatchValues.length === 0);
+  };
+
+  ObjectContaining.prototype.jasmineToString = function() {
+    return "<jasmine.objectContaining(" + j$.pp(this.sample) + ")>";
+  };
+
+  return ObjectContaining;
+};
+
+getJasmineRequireObj().pp = function(j$) {
+
+  function PrettyPrinter() {
+    this.ppNestLevel_ = 0;
+  }
+
+  PrettyPrinter.prototype.format = function(value) {
+    this.ppNestLevel_++;
+    try {
+      if (j$.util.isUndefined(value)) {
+        this.emitScalar('undefined');
+      } else if (value === null) {
+        this.emitScalar('null');
+      } else if (value === j$.getGlobal()) {
+        this.emitScalar('<global>');
+      } else if (value.jasmineToString) {
+        this.emitScalar(value.jasmineToString());
+      } else if (typeof value === 'string') {
+        this.emitString(value);
+      } else if (j$.isSpy(value)) {
+        this.emitScalar("spy on " + value.and.identity());
+      } else if (value instanceof RegExp) {
+        this.emitScalar(value.toString());
+      } else if (typeof value === 'function') {
+        this.emitScalar('Function');
+      } else if (typeof value.nodeType === 'number') {
+        this.emitScalar('HTMLNode');
+      } else if (value instanceof Date) {
+        this.emitScalar('Date(' + value + ')');
+      } else if (value.__Jasmine_been_here_before__) {
+        this.emitScalar('<circular reference: ' + (j$.isArray_(value) ? 'Array' : 'Object') + '>');
+      } else if (j$.isArray_(value) || j$.isA_('Object', value)) {
+        value.__Jasmine_been_here_before__ = true;
+        if (j$.isArray_(value)) {
+          this.emitArray(value);
+        } else {
+          this.emitObject(value);
+        }
+        delete value.__Jasmine_been_here_before__;
+      } else {
+        this.emitScalar(value.toString());
+      }
+    } finally {
+      this.ppNestLevel_--;
+    }
+  };
+
+  PrettyPrinter.prototype.iterateObject = function(obj, fn) {
+    for (var property in obj) {
+      if (!obj.hasOwnProperty(property)) { continue; }
+      if (property == '__Jasmine_been_here_before__') { continue; }
+      fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) &&
+          obj.__lookupGetter__(property) !== null) : false);
+    }
+  };
+
+  PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_;
+  PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_;
+  PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_;
+  PrettyPrinter.prototype.emitString = j$.unimplementedMethod_;
+
+  function StringPrettyPrinter() {
+    PrettyPrinter.call(this);
+
+    this.string = '';
+  }
+
+  j$.util.inherit(StringPrettyPrinter, PrettyPrinter);
+
+  StringPrettyPrinter.prototype.emitScalar = function(value) {
+    this.append(value);
+  };
+
+  StringPrettyPrinter.prototype.emitString = function(value) {
+    this.append("'" + value + "'");
+  };
+
+  StringPrettyPrinter.prototype.emitArray = function(array) {
+    if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
+      this.append("Array");
+      return;
+    }
+
+    this.append('[ ');
+    for (var i = 0; i < array.length; i++) {
+      if (i > 0) {
+        this.append(', ');
+      }
+      this.format(array[i]);
+    }
+    this.append(' ]');
+  };
+
+  StringPrettyPrinter.prototype.emitObject = function(obj) {
+    if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
+      this.append("Object");
+      return;
+    }
+
+    var self = this;
+    this.append('{ ');
+    var first = true;
+
+    this.iterateObject(obj, function(property, isGetter) {
+      if (first) {
+        first = false;
+      } else {
+        self.append(', ');
+      }
+
+      self.append(property);
+      self.append(' : ');
+      if (isGetter) {
+        self.append('<getter>');
+      } else {
+        self.format(obj[property]);
+      }
+    });
+
+    this.append(' }');
+  };
+
+  StringPrettyPrinter.prototype.append = function(value) {
+    this.string += value;
+  };
+
+  return function(value) {
+    var stringPrettyPrinter = new StringPrettyPrinter();
+    stringPrettyPrinter.format(value);
+    return stringPrettyPrinter.string;
+  };
+};
+
+getJasmineRequireObj().QueueRunner = function() {
+
+  function QueueRunner(attrs) {
+    this.fns = attrs.fns || [];
+    this.onComplete = attrs.onComplete || function() {};
+    this.clearStack = attrs.clearStack || function(fn) {fn();};
+    this.onException = attrs.onException || function() {};
+    this.catchException = attrs.catchException || function() { return true; };
+    this.userContext = {};
+  }
+
+  QueueRunner.prototype.execute = function() {
+    this.run(this.fns, 0);
+  };
+
+  QueueRunner.prototype.run = function(fns, recursiveIndex) {
+    var length = fns.length,
+        self = this,
+        iterativeIndex;
+
+    for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) {
+      var fn = fns[iterativeIndex];
+      if (fn.length > 0) {
+        return attemptAsync(fn);
+      } else {
+        attemptSync(fn);
+      }
+    }
+
+    var runnerDone = iterativeIndex >= length;
+
+    if (runnerDone) {
+      this.clearStack(this.onComplete);
+    }
+
+    function attemptSync(fn) {
+      try {
+        fn.call(self.userContext);
+      } catch (e) {
+        handleException(e);
+      }
+    }
+
+    function attemptAsync(fn) {
+      var next = function () { self.run(fns, iterativeIndex + 1); };
+
+      try {
+        fn.call(self.userContext, next);
+      } catch (e) {
+        handleException(e);
+        next();
+      }
+    }
+
+    function handleException(e) {
+      self.onException(e);
+      if (!self.catchException(e)) {
+        //TODO: set a var when we catch an exception and
+        //use a finally block to close the loop in a nice way..
+        throw e;
+      }
+    }
+  };
+
+  return QueueRunner;
+};
+
+getJasmineRequireObj().ReportDispatcher = function() {
+  function ReportDispatcher(methods) {
+
+    var dispatchedMethods = methods || [];
+
+    for (var i = 0; i < dispatchedMethods.length; i++) {
+      var method = dispatchedMethods[i];
+      this[method] = (function(m) {
+        return function() {
+          dispatch(m, arguments);
+        };
+      }(method));
+    }
+
+    var reporters = [];
+
+    this.addReporter = function(reporter) {
+      reporters.push(reporter);
+    };
+
+    return this;
+
+    function dispatch(method, args) {
+      for (var i = 0; i < reporters.length; i++) {
+        var reporter = reporters[i];
+        if (reporter[method]) {
+          reporter[method].apply(reporter, args);
+        }
+      }
+    }
+  }
+
+  return ReportDispatcher;
+};
+
+
+getJasmineRequireObj().SpyStrategy = function() {
+
+  function SpyStrategy(options) {
+    options = options || {};
+
+    var identity = options.name || "unknown",
+        originalFn = options.fn || function() {},
+        getSpy = options.getSpy || function() {},
+        plan = function() {};
+
+    this.identity = function() {
+      return identity;
+    };
+
+    this.exec = function() {
+      return plan.apply(this, arguments);
+    };
+
+    this.callThrough = function() {
+      plan = originalFn;
+      return getSpy();
+    };
+
+    this.returnValue = function(value) {
+      plan = function() {
+        return value;
+      };
+      return getSpy();
+    };
+
+    this.throwError = function(something) {
+      var error = (something instanceof Error) ? something : new Error(something);
+      plan = function() {
+        throw error;
+      };
+      return getSpy();
+    };
+
+    this.callFake = function(fn) {
+      plan = fn;
+      return getSpy();
+    };
+
+    this.stub = function(fn) {
+      plan = function() {};
+      return getSpy();
+    };
+  }
+
+  return SpyStrategy;
+};
+
+getJasmineRequireObj().Suite = function() {
+  function Suite(attrs) {
+    this.env = attrs.env;
+    this.id = attrs.id;
+    this.parentSuite = attrs.parentSuite;
+    this.description = attrs.description;
+    this.onStart = attrs.onStart || function() {};
+    this.resultCallback = attrs.resultCallback || function() {};
+    this.clearStack = attrs.clearStack || function(fn) {fn();};
+
+    this.beforeFns = [];
+    this.afterFns = [];
+    this.queueRunner = attrs.queueRunner || function() {};
+    this.disabled = false;
+
+    this.children = [];
+
+    this.result = {
+      id: this.id,
+      status: this.disabled ? 'disabled' : '',
+      description: this.description,
+      fullName: this.getFullName()
+    };
+  }
+
+  Suite.prototype.getFullName = function() {
+    var fullName = this.description;
+    for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
+      if (parentSuite.parentSuite) {
+        fullName = parentSuite.description + ' ' + fullName;
+      }
+    }
+    return fullName;
+  };
+
+  Suite.prototype.disable = function() {
+    this.disabled = true;
+  };
+
+  Suite.prototype.beforeEach = function(fn) {
+    this.beforeFns.unshift(fn);
+  };
+
+  Suite.prototype.afterEach = function(fn) {
+    this.afterFns.unshift(fn);
+  };
+
+  Suite.prototype.addChild = function(child) {
+    this.children.push(child);
+  };
+
+  Suite.prototype.execute = function(onComplete) {
+    var self = this;
+    if (this.disabled) {
+      complete();
+      return;
+    }
+
+    var allFns = [];
+
+    for (var i = 0; i < this.children.length; i++) {
+      allFns.push(wrapChildAsAsync(this.children[i]));
+    }
+
+    this.onStart(this);
+
+    this.queueRunner({
+      fns: allFns,
+      onComplete: complete
+    });
+
+    function complete() {
+      self.resultCallback(self.result);
+
+      if (onComplete) {
+        onComplete();
+      }
+    }
+
+    function wrapChildAsAsync(child) {
+      return function(done) { child.execute(done); };
+    }
+  };
+
+  return Suite;
+};
+
+if (typeof window == void 0 && typeof exports == "object") {
+  exports.Suite = jasmineRequire.Suite;
+}
+
+getJasmineRequireObj().Timer = function() {
+  function Timer(options) {
+    options = options || {};
+
+    var now = options.now || function() { return new Date().getTime(); },
+        startTime;
+
+    this.start = function() {
+      startTime = now();
+    };
+
+    this.elapsed = function() {
+      return now() - startTime;
+    };
+  }
+
+  return Timer;
+};
+
+getJasmineRequireObj().matchersUtil = function(j$) {
+  // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter?
+
+  return {
+    equals: function(a, b, customTesters) {
+      customTesters = customTesters || [];
+
+      return eq(a, b, [], [], customTesters);
+    },
+
+    contains: function(haystack, needle, customTesters) {
+      customTesters = customTesters || [];
+
+      if (Object.prototype.toString.apply(haystack) === "[object Array]") {
+        for (var i = 0; i < haystack.length; i++) {
+          if (eq(haystack[i], needle, [], [], customTesters)) {
+            return true;
+          }
+        }
+        return false;
+      }
+      return haystack.indexOf(needle) >= 0;
+    },
+
+    buildFailureMessage: function() {
+      var args = Array.prototype.slice.call(arguments, 0),
+        matcherName = args[0],
+        isNot = args[1],
+        actual = args[2],
+        expected = args.slice(3),
+        englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
+
+      var message = "Expected " +
+        j$.pp(actual) +
+        (isNot ? " not " : " ") +
+        englishyPredicate;
+
+      if (expected.length > 0) {
+        for (var i = 0; i < expected.length; i++) {
+          if (i > 0) {
+            message += ",";
+          }
+          message += " " + j$.pp(expected[i]);
+        }
+      }
+
+      return message + ".";
+    }
+  };
+
+  // Equality function lovingly adapted from isEqual in
+  //   [Underscore](http://underscorejs.org)
+  function eq(a, b, aStack, bStack, customTesters) {
+    var result = true;
+
+    for (var i = 0; i < customTesters.length; i++) {
+      var customTesterResult = customTesters[i](a, b);
+      if (!j$.util.isUndefined(customTesterResult)) {
+        return customTesterResult;
+      }
+    }
+
+    if (a instanceof j$.Any) {
+      result = a.jasmineMatches(b);
+      if (result) {
+        return true;
+      }
+    }
+
+    if (b instanceof j$.Any) {
+      result = b.jasmineMatches(a);
+      if (result) {
+        return true;
+      }
+    }
+
+    if (b instanceof j$.ObjectContaining) {
+      result = b.jasmineMatches(a);
+      if (result) {
+        return true;
+      }
+    }
+
+    if (a instanceof Error && b instanceof Error) {
+      return a.message == b.message;
+    }
+
+    // Identical objects are equal. `0 === -0`, but they aren't identical.
+    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
+    if (a === b) { return a !== 0 || 1 / a == 1 / b; }
+    // A strict comparison is necessary because `null == undefined`.
+    if (a === null || b === null) { return a === b; }
+    var className = Object.prototype.toString.call(a);
+    if (className != Object.prototype.toString.call(b)) { return false; }
+    switch (className) {
+      // Strings, numbers, dates, and booleans are compared by value.
+      case '[object String]':
+        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+        // equivalent to `new String("5")`.
+        return a == String(b);
+      case '[object Number]':
+        // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
+        // other numeric values.
+        return a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b);
+      case '[object Date]':
+      case '[object Boolean]':
+        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+        // millisecond representations. Note that invalid dates with millisecond representations
+        // of `NaN` are not equivalent.
+        return +a == +b;
+      // RegExps are compared by their source patterns and flags.
+      case '[object RegExp]':
+        return a.source == b.source &&
+          a.global == b.global &&
+          a.multiline == b.multiline &&
+          a.ignoreCase == b.ignoreCase;
+    }
+    if (typeof a != 'object' || typeof b != 'object') { return false; }
+    // Assume equality for cyclic structures. The algorithm for detecting cyclic
+    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+    var length = aStack.length;
+    while (length--) {
+      // Linear search. Performance is inversely proportional to the number of
+      // unique nested structures.
+      if (aStack[length] == a) { return bStack[length] == b; }
+    }
+    // Add the first object to the stack of traversed objects.
+    aStack.push(a);
+    bStack.push(b);
+    var size = 0;
+    // Recursively compare objects and arrays.
+    if (className == '[object Array]') {
+      // Compare array lengths to determine if a deep comparison is necessary.
+      size = a.length;
+      result = size == b.length;
+      if (result) {
+        // Deep compare the contents, ignoring non-numeric properties.
+        while (size--) {
+          if (!(result = eq(a[size], b[size], aStack, bStack, customTesters))) { break; }
+        }
+      }
+    } else {
+      // Objects with different constructors are not equivalent, but `Object`s
+      // from different frames are.
+      var aCtor = a.constructor, bCtor = b.constructor;
+      if (aCtor !== bCtor && !(isFunction(aCtor) && (aCtor instanceof aCtor) &&
+        isFunction(bCtor) && (bCtor instanceof bCtor))) {
+        return false;
+      }
+      // Deep compare objects.
+      for (var key in a) {
+        if (has(a, key)) {
+          // Count the expected number of properties.
+          size++;
+          // Deep compare each member.
+          if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters))) { break; }
+        }
+      }
+      // Ensure that both objects contain the same number of properties.
+      if (result) {
+        for (key in b) {
+          if (has(b, key) && !(size--)) { break; }
+        }
+        result = !size;
+      }
+    }
+    // Remove the first object from the stack of traversed objects.
+    aStack.pop();
+    bStack.pop();
+
+    return result;
+
+    function has(obj, key) {
+      return obj.hasOwnProperty(key);
+    }
+
+    function isFunction(obj) {
+      return typeof obj === 'function';
+    }
+  }
+};
+
+getJasmineRequireObj().toBe = function() {
+  function toBe() {
+    return {
+      compare: function(actual, expected) {
+        return {
+          pass: actual === expected
+        };
+      }
+    };
+  }
+
+  return toBe;
+};
+
+getJasmineRequireObj().toBeCloseTo = function() {
+
+  function toBeCloseTo() {
+    return {
+      compare: function(actual, expected, precision) {
+        if (precision !== 0) {
+          precision = precision || 2;
+        }
+
+        return {
+          pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2)
+        };
+      }
+    };
+  }
+
+  return toBeCloseTo;
+};
+
+getJasmineRequireObj().toBeDefined = function() {
+  function toBeDefined() {
+    return {
+      compare: function(actual) {
+        return {
+          pass: (void 0 !== actual)
+        };
+      }
+    };
+  }
+
+  return toBeDefined;
+};
+
+getJasmineRequireObj().toBeFalsy = function() {
+  function toBeFalsy() {
+    return {
+      compare: function(actual) {
+        return {
+          pass: !!!actual
+        };
+      }
+    };
+  }
+
+  return toBeFalsy;
+};
+
+getJasmineRequireObj().toBeGreaterThan = function() {
+
+  function toBeGreaterThan() {
+    return {
+      compare: function(actual, expected) {
+        return {
+          pass: actual > expected
+        };
+      }
+    };
+  }
+
+  return toBeGreaterThan;
+};
+
+
+getJasmineRequireObj().toBeLessThan = function() {
+  function toBeLessThan() {
+    return {
+
+      compare: function(actual, expected) {
+        return {
+          pass: actual < expected
+        };
+      }
+    };
+  }
+
+  return toBeLessThan;
+};
+getJasmineRequireObj().toBeNaN = function(j$) {
+
+  function toBeNaN() {
+    return {
+      compare: function(actual) {
+        var result = {
+          pass: (actual !== actual)
+        };
+
+        if (result.pass) {
+          result.message = "Expected actual not to be NaN.";
+        } else {
+          result.message = "Expected " + j$.pp(actual) + " to be NaN.";
+        }
+
+        return result;
+      }
+    };
+  }
+
+  return toBeNaN;
+};
+
+getJasmineRequireObj().toBeNull = function() {
+
+  function toBeNull() {
+    return {
+      compare: function(actual) {
+        return {
+          pass: actual === null
+        };
+      }
+    };
+  }
+
+  return toBeNull;
+};
+
+getJasmineRequireObj().toBeTruthy = function() {
+
+  function toBeTruthy() {
+    return {
+      compare: function(actual) {
+        return {
+          pass: !!actual
+        };
+      }
+    };
+  }
+
+  return toBeTruthy;
+};
+
+getJasmineRequireObj().toBeUndefined = function() {
+
+  function toBeUndefined() {
+    return {
+      compare: function(actual) {
+        return {
+          pass: void 0 === actual
+        };
+      }
+    };
+  }
+
+  return toBeUndefined;
+};
+
+getJasmineRequireObj().toContain = function() {
+  function toContain(util, customEqualityTesters) {
+    customEqualityTesters = customEqualityTesters || [];
+
+    return {
+      compare: function(actual, expected) {
+
+        return {
+          pass: util.contains(actual, expected, customEqualityTesters)
+        };
+      }
+    };
+  }
+
+  return toContain;
+};
+
+getJasmineRequireObj().toEqual = function() {
+
+  function toEqual(util, customEqualityTesters) {
+    customEqualityTesters = customEqualityTesters || [];
+
+    return {
+      compare: function(actual, expected) {
+        var result = {
+          pass: false
+        };
+
+        result.pass = util.equals(actual, expected, customEqualityTesters);
+
+        return result;
+      }
+    };
+  }
+
+  return toEqual;
+};
+
+getJasmineRequireObj().toHaveBeenCalled = function(j$) {
+
+  function toHaveBeenCalled() {
+    return {
+      compare: function(actual) {
+        var result = {};
+
+        if (!j$.isSpy(actual)) {
+          throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.');
+        }
+
+        if (arguments.length > 1) {
+          throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
+        }
+
+        result.pass = actual.calls.any();
+
+        result.message = result.pass ?
+          "Expected spy " + actual.and.identity() + " not to have been called." :
+          "Expected spy " + actual.and.identity() + " to have been called.";
+
+        return result;
+      }
+    };
+  }
+
+  return toHaveBeenCalled;
+};
+
+getJasmineRequireObj().toHaveBeenCalledWith = function(j$) {
+
+  function toHaveBeenCalledWith(util) {
+    return {
+      compare: function() {
+        var args = Array.prototype.slice.call(arguments, 0),
+          actual = args[0],
+          expectedArgs = args.slice(1),
+          result = { pass: false };
+
+        if (!j$.isSpy(actual)) {
+          throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.');
+        }
+
+        if (!actual.calls.any()) {
+          result.message = "Expected spy " + actual.and.identity() + " to have been called with " + j$.pp(expectedArgs) + " but it was never called.";
+          return result;
+        }
+
+        if (util.contains(actual.calls.allArgs(), expectedArgs)) {
+          result.pass = true;
+          result.message = "Expected spy " + actual.and.identity() + " not to have been called with " + j$.pp(expectedArgs) + " but it was.";
+        } else {
+          result.message = "Expected spy " + actual.and.identity() + " to have been called with " + j$.pp(expectedArgs) + " but actual calls were " + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + ".";
+        }
+
+        return result;
+      }
+    };
+  }
+
+  return toHaveBeenCalledWith;
+};
+
+getJasmineRequireObj().toMatch = function() {
+
+  function toMatch() {
+    return {
+      compare: function(actual, expected) {
+        var regexp = new RegExp(expected);
+
+        return {
+          pass: regexp.test(actual)
+        };
+      }
+    };
+  }
+
+  return toMatch;
+};
+
+getJasmineRequireObj().toThrow = function(j$) {
+
+  function toThrow(util) {
+    return {
+      compare: function(actual, expected) {
+        var result = { pass: false },
+          threw = false,
+          thrown;
+
+        if (typeof actual != "function") {
+          throw new Error("Actual is not a Function");
+        }
+
+        try {
+          actual();
+        } catch (e) {
+          threw = true;
+          thrown = e;
+        }
+
+        if (!threw) {
+          result.message = "Expected function to throw an exception.";
+          return result;
+        }
+
+        if (arguments.length == 1) {
+          result.pass = true;
+          result.message = "Expected function not to throw, but it threw " + j$.pp(thrown) + ".";
+
+          return result;
+        }
+
+        if (util.equals(thrown, expected)) {
+          result.pass = true;
+          result.message = "Expected function not to throw " + j$.pp(expected) + ".";
+        } else {
+          result.message = "Expected function to throw " + j$.pp(expected) + ", but it threw " +  j$.pp(thrown) + ".";
+        }
+
+        return result;
+      }
+    };
+  }
+
+  return toThrow;
+};
+
+getJasmineRequireObj().toThrowError = function(j$) {
+  function toThrowError (util) {
+    return {
+      compare: function(actual) {
+        var threw = false,
+          thrown,
+          errorType,
+          message,
+          regexp,
+          name,
+          constructorName;
+
+        if (typeof actual != "function") {
+          throw new Error("Actual is not a Function");
+        }
+
+        extractExpectedParams.apply(null, arguments);
+
+        try {
+          actual();
+        } catch (e) {
+          threw = true;
+          thrown = e;
+        }
+
+        if (!threw) {
+          return fail("Expected function to throw an Error.");
+        }
+
+        if (!(thrown instanceof Error)) {
+          return fail("Expected function to throw an Error, but it threw " + thrown + ".");
+        }
+
+        if (arguments.length == 1) {
+          return pass("Expected function not to throw an Error, but it threw " + fnNameFor(thrown) + ".");
+        }
+
+        if (errorType) {
+          name = fnNameFor(errorType);
+          constructorName = fnNameFor(thrown.constructor);
+        }
+
+        if (errorType && message) {
+          if (thrown.constructor == errorType && util.equals(thrown.message, message)) {
+            return pass("Expected function not to throw " + name + " with message \"" + message + "\".");
+          } else {
+            return fail("Expected function to throw " + name + " with message \"" + message +
+                        "\", but it threw " + constructorName + " with message \"" + thrown.message + "\".");
+          }
+        }
+
+        if (errorType && regexp) {
+          if (thrown.constructor == errorType && regexp.test(thrown.message)) {
+            return pass("Expected function not to throw " + name + " with message matching " + regexp + ".");
+          } else {
+            return fail("Expected function to throw " + name + " with message matching " + regexp +
+                        ", but it threw " + constructorName + " with message \"" + thrown.message + "\".");
+          }
+        }
+
+        if (errorType) {
+          if (thrown.constructor == errorType) {
+            return pass("Expected function not to throw " + name + ".");
+          } else {
+            return fail("Expected function to throw " + name + ", but it threw " + constructorName + ".");
+          }
+        }
+
+        if (message) {
+          if (thrown.message == message) {
+            return pass("Expected function not to throw an exception with message " + j$.pp(message) + ".");
+          } else {
+            return fail("Expected function to throw an exception with message " + j$.pp(message) +
+                        ", but it threw an exception with message " + j$.pp(thrown.message) + ".");
+          }
+        }
+
+        if (regexp) {
+          if (regexp.test(thrown.message)) {
+            return pass("Expected function not to throw an exception with a message matching " + j$.pp(regexp) + ".");
+          } else {
+            return fail("Expected function to throw an exception with a message matching " + j$.pp(regexp) +
+                        ", but it threw an exception with message " + j$.pp(thrown.message) + ".");
+          }
+        }
+
+        function fnNameFor(func) {
+            return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1];
+        }
+
+        function pass(notMessage) {
+          return {
+            pass: true,
+            message: notMessage
+          };
+        }
+
+        function fail(message) {
+          return {
+            pass: false,
+            message: message
+          };
+        }
+
+        function extractExpectedParams() {
+          if (arguments.length == 1) {
+            return;
+          }
+
+          if (arguments.length == 2) {
+            var expected = arguments[1];
+
+            if (expected instanceof RegExp) {
+              regexp = expected;
+            } else if (typeof expected == "string") {
+              message = expected;
+            } else if (checkForAnErrorType(expected)) {
+              errorType = expected;
+            }
+
+            if (!(errorType || message || regexp)) {
+              throw new Error("Expected is not an Error, string, or RegExp.");
+            }
+          } else {
+            if (checkForAnErrorType(arguments[1])) {
+              errorType = arguments[1];
+            } else {
+              throw new Error("Expected error type is not an Error.");
+            }
+
+            if (arguments[2] instanceof RegExp) {
+              regexp = arguments[2];
+            } else if (typeof arguments[2] == "string") {
+              message = arguments[2];
+            } else {
+              throw new Error("Expected error message is not a string or RegExp.");
+            }
+          }
+        }
+
+        function checkForAnErrorType(type) {
+          if (typeof type !== "function") {
+            return false;
+          }
+
+          var Surrogate = function() {};
+          Surrogate.prototype = type.prototype;
+          return (new Surrogate()) instanceof Error;
+        }
+      }
+    };
+  }
+
+  return toThrowError;
+};
+
+getJasmineRequireObj().version = function() {
+  return "2.0.0";
+};
diff --git a/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/jasmine_favicon.png b/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/jasmine_favicon.png
new file mode 100755
index 0000000000000000000000000000000000000000..3562e278f108d0f6a918d198f21e055e601c7e71
Binary files /dev/null and b/src/main/webapp/scripts/lib/yamljs/test/lib/jasmine-2.0.0/jasmine_favicon.png differ
diff --git a/src/main/webapp/scripts/lib/yamljs/test/spec/YamlSpec.coffee b/src/main/webapp/scripts/lib/yamljs/test/spec/YamlSpec.coffee
new file mode 100755
index 0000000000000000000000000000000000000000..0d0f613234b59554d11821f61d09835ea4906189
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/test/spec/YamlSpec.coffee
@@ -0,0 +1,1471 @@
+
+unless YAML?
+    YAML = require '../../src/Yaml'
+
+
+# Parsing
+#
+
+describe 'Parsed YAML Collections', ->
+
+    it 'can be simple sequence', ->
+
+        expect YAML.parse """
+        - apple
+        - banana
+        - carrot
+        """
+        .toEqual ['apple', 'banana', 'carrot']
+
+
+    it 'can be nested sequences', ->
+
+        expect YAML.parse """
+        -
+         - foo
+         - bar
+         - baz
+        """
+        .toEqual [['foo', 'bar', 'baz']]
+
+
+    it 'can be mixed sequences', ->
+
+        expect YAML.parse """
+        - apple
+        -
+         - foo
+         - bar
+         - x123
+        - banana
+        - carrot
+        """
+        .toEqual ['apple', ['foo', 'bar', 'x123'], 'banana', 'carrot']
+
+
+    it 'can be deeply nested sequences', ->
+
+        expect YAML.parse """
+        -
+         -
+          - uno
+          - dos
+        """
+        .toEqual [[['uno', 'dos']]]
+
+
+    it 'can be simple mapping', ->
+
+        expect YAML.parse """
+        foo: whatever
+        bar: stuff
+        """
+        .toEqual foo: 'whatever', bar: 'stuff'
+
+
+    it 'can be sequence in a mapping', ->
+
+        expect YAML.parse """
+        foo: whatever
+        bar:
+         - uno
+         - dos
+        """
+        .toEqual foo: 'whatever', bar: ['uno', 'dos']
+
+
+    it 'can be nested mappings', ->
+
+        expect YAML.parse """
+        foo: whatever
+        bar:
+         fruit: apple
+         name: steve
+         sport: baseball
+        """
+        .toEqual foo: 'whatever', bar: (fruit: 'apple', name: 'steve', sport: 'baseball')
+
+
+    it 'can be mixed mapping', ->
+
+        expect YAML.parse """
+        foo: whatever
+        bar:
+         -
+           fruit: apple
+           name: steve
+           sport: baseball
+         - more
+         -
+           python: rocks
+           perl: papers
+           ruby: scissorses
+        """
+        .toEqual foo: 'whatever', bar: [
+            (fruit: 'apple', name: 'steve', sport: 'baseball'),
+            'more',
+            (python: 'rocks', perl: 'papers', ruby: 'scissorses')
+        ]
+
+
+    it 'can have mapping-in-sequence shortcut', ->
+
+        expect YAML.parse """
+        - work on YAML.py:
+           - work on Store
+        """
+        .toEqual [('work on YAML.py': ['work on Store'])]
+
+
+    it 'can have unindented sequence-in-mapping shortcut', ->
+
+        expect YAML.parse """
+        allow:
+        - 'localhost'
+        - '%.sourceforge.net'
+        - '%.freepan.org'
+        """
+        .toEqual (allow: ['localhost', '%.sourceforge.net', '%.freepan.org'])
+
+
+    it 'can merge key', ->
+
+        expect YAML.parse """
+        mapping:
+          name: Joe
+          job: Accountant
+          <<:
+            age: 38
+        """
+        .toEqual mapping:
+                    name: 'Joe'
+                    job: 'Accountant'
+                    age: 38
+
+    it 'can ignore trailing empty lines for smallest indent', ->
+
+        expect YAML.parse """ trailing: empty lines\n"""
+        .toEqual trailing: 'empty lines'
+
+describe 'Parsed YAML Inline Collections', ->
+
+    it 'can be simple inline array', ->
+
+        expect YAML.parse """
+        ---
+        seq: [ a, b, c ]
+        """
+        .toEqual seq: ['a', 'b', 'c']
+
+
+    it 'can be simple inline hash', ->
+
+        expect YAML.parse """
+        ---
+        hash: { name: Steve, foo: bar }
+        """
+        .toEqual hash: (name: 'Steve', foo: 'bar')
+
+
+    it 'can be nested inline hash', ->
+
+        expect YAML.parse """
+        ---
+        hash: { val1: "string", val2: { v2k1: "v2k1v" } }
+        """
+        .toEqual hash: (val1: 'string', val2: (v2k1: 'v2k1v'))
+
+
+    it 'can be multi-line inline collections', ->
+
+        expect YAML.parse """
+        languages: [ Ruby,
+                     Perl,
+                     Python ]
+        websites: { YAML: yaml.org,
+                    Ruby: ruby-lang.org,
+                    Python: python.org,
+                    Perl: use.perl.org }
+        """
+        .toEqual (
+            languages: ['Ruby', 'Perl', 'Python']
+            websites:
+                YAML: 'yaml.org'
+                Ruby: 'ruby-lang.org'
+                Python: 'python.org'
+                Perl: 'use.perl.org'
+        )
+
+
+
+describe 'Parsed YAML Basic Types', ->
+
+    it 'can be strings', ->
+
+        expect YAML.parse """
+        ---
+        String
+        """
+        .toEqual 'String'
+
+
+    it 'can be double-quoted strings with backslashes', ->
+
+        expect YAML.parse """
+        str:
+            "string with \\\\ inside"
+        """
+        .toEqual str: 'string with \\ inside'
+
+
+    it 'can be single-quoted strings with backslashes', ->
+
+        expect YAML.parse """
+        str:
+            'string with \\\\ inside'
+        """
+        .toEqual str: 'string with \\\\ inside'
+
+
+    it 'can be double-quoted strings with line breaks', ->
+
+        expect YAML.parse """
+        str:
+            "string with \\n inside"
+        """
+        .toEqual str: 'string with \n inside'
+
+
+    it 'can be single-quoted strings with escaped line breaks', ->
+
+        expect YAML.parse """
+        str:
+            'string with \\n inside'
+        """
+        .toEqual str: 'string with \\n inside'
+
+
+    it 'can be double-quoted strings with line breaks and backslashes', ->
+
+        expect YAML.parse """
+        str:
+            "string with \\n inside and \\\\ also"
+        """
+        .toEqual str: 'string with \n inside and \\ also'
+
+
+    it 'can be single-quoted strings with line breaks and backslashes', ->
+
+        expect YAML.parse """
+        str:
+            'string with \\n inside and \\\\ also'
+        """
+        .toEqual str: 'string with \\n inside and \\\\ also'
+
+
+    it 'can have string characters in sequences', ->
+
+        expect YAML.parse """
+        - What's Yaml?
+        - It's for writing data structures in plain text.
+        - And?
+        - And what? That's not good enough for you?
+        - No, I mean, "And what about Yaml?"
+        - Oh, oh yeah. Uh.. Yaml for JavaScript.
+        """
+        .toEqual [
+            "What's Yaml?",
+            "It's for writing data structures in plain text.",
+            "And?",
+            "And what? That's not good enough for you?",
+            "No, I mean, \"And what about Yaml?\"",
+            "Oh, oh yeah. Uh.. Yaml for JavaScript."
+        ]
+
+
+    it 'can have indicators in strings', ->
+
+        expect YAML.parse """
+        the colon followed by space is an indicator: but is a string:right here
+        same for the pound sign: here we have it#in a string
+        the comma can, honestly, be used in most cases: [ but not in, inline collections ]
+        """
+        .toEqual (
+            'the colon followed by space is an indicator': 'but is a string:right here',
+            'same for the pound sign': 'here we have it#in a string',
+            'the comma can, honestly, be used in most cases': ['but not in', 'inline collections']
+        )
+
+
+    it 'can force strings', ->
+
+        expect YAML.parse """
+        date string: !str 2001-08-01
+        number string: !str 192
+        date string 2: !!str 2001-08-01
+        number string 2: !!str 192
+        """
+        .toEqual (
+            'date string': '2001-08-01',
+            'number string': '192' ,
+            'date string 2': '2001-08-01',
+            'number string 2': '192'
+        )
+
+
+    it 'can be single-quoted strings', ->
+
+        expect YAML.parse """
+        all my favorite symbols: '#:!/%.)'
+        a few i hate: '&(*'
+        why do i hate them?: 'it''s very hard to explain'
+        """
+        .toEqual (
+            'all my favorite symbols': '#:!/%.)',
+            'a few i hate': '&(*',
+            'why do i hate them?': 'it\'s very hard to explain'
+        )
+
+
+    it 'can be double-quoted strings', ->
+
+        expect YAML.parse """
+        i know where i want my line breaks: "one here\\nand another here\\n"
+        """
+        .toEqual (
+            'i know where i want my line breaks': "one here\nand another here\n"
+        )
+
+
+    it 'can be null', ->
+
+        expect YAML.parse """
+        name: Mr. Show
+        hosted by: Bob and David
+        date of next season: ~
+        """
+        .toEqual (
+            'name': 'Mr. Show'
+            'hosted by': 'Bob and David'
+            'date of next season': null
+        )
+
+
+    it 'can be boolean', ->
+
+        expect YAML.parse """
+        Is Gus a Liar?: true
+        Do I rely on Gus for Sustenance?: false
+        """
+        .toEqual (
+            'Is Gus a Liar?': true
+            'Do I rely on Gus for Sustenance?': false
+        )
+
+
+    it 'can be integers', ->
+
+        expect YAML.parse """
+        zero: 0
+        simple: 12
+        one-thousand: 1,000
+        negative one-thousand: -1,000
+        """
+        .toEqual (
+            'zero': 0
+            'simple': 12
+            'one-thousand': 1000
+            'negative one-thousand': -1000
+        )
+
+
+    it 'can be integers as map keys', ->
+
+        expect YAML.parse """
+        1: one
+        2: two
+        3: three
+        """
+        .toEqual (
+            1: 'one'
+            2: 'two'
+            3: 'three'
+        )
+
+
+    it 'can be floats', ->
+
+        expect YAML.parse """
+        a simple float: 2.00
+        larger float: 1,000.09
+        scientific notation: 1.00009e+3
+        """
+        .toEqual (
+            'a simple float': 2.0
+            'larger float': 1000.09
+            'scientific notation': 1000.09
+        )
+
+
+    it 'can be time', ->
+
+        iso8601Date = new Date Date.UTC(2001, 12-1, 14, 21, 59, 43, 10)
+        iso8601Date.setTime iso8601Date.getTime() - 5 * 3600 * 1000
+
+        spaceSeparatedDate = new Date Date.UTC(2001, 12-1, 14, 21, 59, 43, 10)
+        spaceSeparatedDate.setTime spaceSeparatedDate.getTime() - 5 * 3600 * 1000
+
+        withDatesToTime = (input) ->
+            res = {}
+            for key, val of input
+                res[key] = Math.round(val.getTime() / 1000) * 1000
+            return res
+
+        expect withDatesToTime(YAML.parse """
+            iso8601: 2001-12-14t21:59:43.10-05:00
+            space seperated: 2001-12-14 21:59:43.10 -05:00
+        """)
+        .toEqual withDatesToTime (
+            'iso8601': iso8601Date
+            'space seperated': spaceSeparatedDate
+        )
+
+
+    it 'can be date', ->
+
+        aDate = new Date Date.UTC(1976, 7-1, 31, 0, 0, 0, 0)
+
+        withDatesToTime = (input) ->
+            return input
+            res = {}
+            for key, val of input
+                res[key] = Math.round(val.getTime() / 1000) * 1000
+            return res
+
+        expect withDatesToTime(YAML.parse """
+            date: 1976-07-31
+        """)
+        .toEqual withDatesToTime (
+            'date': aDate
+        )
+
+
+
+describe 'Parsed YAML Blocks', ->
+
+    it 'can be single ending newline', ->
+
+        expect YAML.parse """
+        ---
+        this: |
+            Foo
+            Bar
+        """
+        .toEqual 'this': "Foo\nBar\n"
+
+
+    it 'can be single ending newline with \'+\' indicator', ->
+
+        expect YAML.parse """
+        normal: |
+          extra new lines not kept
+
+        preserving: |+
+          extra new lines are kept
+
+
+        dummy: value
+        """
+        .toEqual (
+            'normal': "extra new lines not kept\n"
+            'preserving': "extra new lines are kept\n\n\n"
+            'dummy': 'value'
+        )
+
+
+    it 'can be multi-line block handling trailing newlines in function of \'+\', \'-\' indicators', ->
+
+        expect YAML.parse """
+        clipped: |
+            This has one newline.
+
+
+
+        same as "clipped" above: "This has one newline.\\n"
+
+        stripped: |-
+            This has no newline.
+
+
+
+        same as "stripped" above: "This has no newline."
+
+        kept: |+
+            This has four newlines.
+
+
+
+        same as "kept" above: "This has four newlines.\\n\\n\\n\\n"
+        """
+        .toEqual (
+            'clipped': "This has one newline.\n"
+            'same as "clipped" above': "This has one newline.\n"
+            'stripped':'This has no newline.'
+            'same as "stripped" above': 'This has no newline.'
+            'kept': "This has four newlines.\n\n\n\n"
+            'same as "kept" above': "This has four newlines.\n\n\n\n"
+        )
+
+
+    it 'can be folded block in a sequence', ->
+
+        expect YAML.parse """
+        ---
+        - apple
+        - banana
+        - >
+            can't you see
+            the beauty of yaml?
+            hmm
+        - dog
+        """
+        .toEqual [
+            'apple',
+            'banana',
+            "can't you see the beauty of yaml? hmm\n",
+            'dog'
+        ]
+
+
+    it 'can be folded block as a mapping value', ->
+
+        expect YAML.parse """
+        ---
+        quote: >
+            Mark McGwire's
+            year was crippled
+            by a knee injury.
+        source: espn
+        """
+        .toEqual (
+            'quote': "Mark McGwire's year was crippled by a knee injury.\n"
+            'source': 'espn'
+        )
+
+
+    it 'can be folded block handling trailing newlines in function of \'+\', \'-\' indicators', ->
+
+        expect YAML.parse """
+        clipped: >
+            This has one newline.
+
+
+
+        same as "clipped" above: "This has one newline.\\n"
+
+        stripped: >-
+            This has no newline.
+
+
+
+        same as "stripped" above: "This has no newline."
+
+        kept: >+
+            This has four newlines.
+
+
+
+        same as "kept" above: "This has four newlines.\\n\\n\\n\\n"
+        """
+        .toEqual (
+            'clipped': "This has one newline.\n"
+            'same as "clipped" above': "This has one newline.\n"
+            'stripped': 'This has no newline.'
+            'same as "stripped" above': 'This has no newline.'
+            'kept': "This has four newlines.\n\n\n\n"
+            'same as "kept" above': "This has four newlines.\n\n\n\n"
+        )
+
+
+    it 'can be the whole document as intented block', ->
+
+        expect YAML.parse """
+        ---
+          foo: "bar"
+          baz:
+            - "qux"
+            - "quxx"
+          corge: null
+        """
+        .toEqual (
+            'foo': "bar"
+            'baz': ['qux', 'quxx']
+            'corge': null
+        )
+
+
+
+
+describe 'Parsed YAML Comments', ->
+
+    it 'can begin the document', ->
+
+        expect YAML.parse """
+        # This is a comment
+        hello: world
+        """
+        .toEqual (
+            hello: 'world'
+        )
+
+
+    it 'can be less indented in mapping', ->
+
+        expect YAML.parse """
+        parts:
+            a: 'b'
+            # normally indented comment
+            c: 'd'
+        # less indented comment
+            e: 'f'
+        """
+        .toEqual (
+            parts: {a: 'b', c: 'd', e: 'f'}
+        )
+
+
+    it 'can be less indented in sequence', ->
+
+        expect YAML.parse """
+        list-header:
+          - item1
+        #  - item2
+          - item3
+          # - item4
+        """
+        .toEqual (
+            'list-header': ['item1', 'item3']
+        )
+
+
+    it 'can finish a line', ->
+
+        expect YAML.parse """
+        hello: world # This is a comment
+        """
+        .toEqual (
+            hello: 'world'
+        )
+
+
+    it 'can end the document', ->
+
+        expect YAML.parse """
+        hello: world
+        # This is a comment
+        """
+        .toEqual (
+            hello: 'world'
+        )
+
+
+
+describe 'Parsed YAML Aliases and Anchors', ->
+
+    it 'can be simple alias', ->
+
+        expect YAML.parse """
+        - &showell Steve
+        - Clark
+        - Brian
+        - Oren
+        - *showell
+        """
+        .toEqual ['Steve', 'Clark', 'Brian', 'Oren', 'Steve']
+
+
+    it 'can be alias of a mapping', ->
+
+        expect YAML.parse """
+        - &hello
+            Meat: pork
+            Starch: potato
+        - banana
+        - *hello
+        """
+        .toEqual [
+            Meat: 'pork', Starch: 'potato'
+        ,
+            'banana'
+        ,
+            Meat: 'pork', Starch: 'potato'
+        ]
+
+
+
+describe 'Parsed YAML Documents', ->
+
+    it 'can have YAML header', ->
+
+        expect YAML.parse """
+        --- %YAML:1.0
+        foo: 1
+        bar: 2
+        """
+        .toEqual (
+            foo: 1
+            bar: 2
+        )
+
+
+    it 'can have leading document separator', ->
+
+        expect YAML.parse """
+        ---
+        - foo: 1
+          bar: 2
+        """
+        .toEqual [(
+            foo: 1
+            bar: 2
+        )]
+
+
+    it 'can have multiple document separators in block', ->
+
+        expect YAML.parse """
+        foo: |
+            ---
+            foo: bar
+            ---
+            yo: baz
+        bar: |
+            fooness
+        """
+        .toEqual (
+           foo: "---\nfoo: bar\n---\nyo: baz\n"
+           bar: "fooness\n"
+        )
+
+
+# Dumping
+#
+
+describe 'Dumped YAML Collections', ->
+
+    it 'can be simple sequence', ->
+
+        expect YAML.parse """
+        - apple
+        - banana
+        - carrot
+        """
+        .toEqual YAML.parse YAML.dump ['apple', 'banana', 'carrot']
+
+
+    it 'can be nested sequences', ->
+
+        expect YAML.parse """
+        -
+         - foo
+         - bar
+         - baz
+        """
+        .toEqual YAML.parse YAML.dump [['foo', 'bar', 'baz']]
+
+
+    it 'can be mixed sequences', ->
+
+        expect YAML.parse """
+        - apple
+        -
+         - foo
+         - bar
+         - x123
+        - banana
+        - carrot
+        """
+        .toEqual YAML.parse YAML.dump ['apple', ['foo', 'bar', 'x123'], 'banana', 'carrot']
+
+
+    it 'can be deeply nested sequences', ->
+
+        expect YAML.parse """
+        -
+         -
+          - uno
+          - dos
+        """
+        .toEqual YAML.parse YAML.dump [[['uno', 'dos']]]
+
+
+    it 'can be simple mapping', ->
+
+        expect YAML.parse """
+        foo: whatever
+        bar: stuff
+        """
+        .toEqual YAML.parse YAML.dump foo: 'whatever', bar: 'stuff'
+
+
+    it 'can be sequence in a mapping', ->
+
+        expect YAML.parse """
+        foo: whatever
+        bar:
+         - uno
+         - dos
+        """
+        .toEqual YAML.parse YAML.dump foo: 'whatever', bar: ['uno', 'dos']
+
+
+    it 'can be nested mappings', ->
+
+        expect YAML.parse """
+        foo: whatever
+        bar:
+         fruit: apple
+         name: steve
+         sport: baseball
+        """
+        .toEqual YAML.parse YAML.dump foo: 'whatever', bar: (fruit: 'apple', name: 'steve', sport: 'baseball')
+
+
+    it 'can be mixed mapping', ->
+
+        expect YAML.parse """
+        foo: whatever
+        bar:
+         -
+           fruit: apple
+           name: steve
+           sport: baseball
+         - more
+         -
+           python: rocks
+           perl: papers
+           ruby: scissorses
+        """
+        .toEqual YAML.parse YAML.dump foo: 'whatever', bar: [
+            (fruit: 'apple', name: 'steve', sport: 'baseball'),
+            'more',
+            (python: 'rocks', perl: 'papers', ruby: 'scissorses')
+        ]
+
+
+    it 'can have mapping-in-sequence shortcut', ->
+
+        expect YAML.parse """
+        - work on YAML.py:
+           - work on Store
+        """
+        .toEqual YAML.parse YAML.dump [('work on YAML.py': ['work on Store'])]
+
+
+    it 'can have unindented sequence-in-mapping shortcut', ->
+
+        expect YAML.parse """
+        allow:
+        - 'localhost'
+        - '%.sourceforge.net'
+        - '%.freepan.org'
+        """
+        .toEqual YAML.parse YAML.dump (allow: ['localhost', '%.sourceforge.net', '%.freepan.org'])
+
+
+    it 'can merge key', ->
+
+        expect YAML.parse """
+        mapping:
+          name: Joe
+          job: Accountant
+          <<:
+            age: 38
+        """
+        .toEqual YAML.parse YAML.dump mapping:
+                    name: 'Joe'
+                    job: 'Accountant'
+                    age: 38
+
+
+
+describe 'Dumped YAML Inline Collections', ->
+
+    it 'can be simple inline array', ->
+
+        expect YAML.parse """
+        ---
+        seq: [ a, b, c ]
+        """
+        .toEqual YAML.parse YAML.dump seq: ['a', 'b', 'c']
+
+
+    it 'can be simple inline hash', ->
+
+        expect YAML.parse """
+        ---
+        hash: { name: Steve, foo: bar }
+        """
+        .toEqual YAML.parse YAML.dump hash: (name: 'Steve', foo: 'bar')
+
+
+    it 'can be multi-line inline collections', ->
+
+        expect YAML.parse """
+        languages: [ Ruby,
+                     Perl,
+                     Python ]
+        websites: { YAML: yaml.org,
+                    Ruby: ruby-lang.org,
+                    Python: python.org,
+                    Perl: use.perl.org }
+        """
+        .toEqual YAML.parse YAML.dump (
+            languages: ['Ruby', 'Perl', 'Python']
+            websites:
+                YAML: 'yaml.org'
+                Ruby: 'ruby-lang.org'
+                Python: 'python.org'
+                Perl: 'use.perl.org'
+        )
+
+    it 'can be dumped empty sequences in mappings', ->
+
+        expect YAML.parse(YAML.dump({key:[]}))
+        .toEqual({key:[]})
+
+
+
+describe 'Dumped YAML Basic Types', ->
+
+    it 'can be strings', ->
+
+        expect YAML.parse """
+        ---
+        String
+        """
+        .toEqual YAML.parse YAML.dump 'String'
+
+
+    it 'can be double-quoted strings with backslashes', ->
+
+        expect YAML.parse """
+        str:
+            "string with \\\\ inside"
+        """
+        .toEqual YAML.parse YAML.dump str: 'string with \\ inside'
+
+
+    it 'can be single-quoted strings with backslashes', ->
+
+        expect YAML.parse """
+        str:
+            'string with \\\\ inside'
+        """
+        .toEqual YAML.parse YAML.dump str: 'string with \\\\ inside'
+
+
+    it 'can be double-quoted strings with line breaks', ->
+
+        expect YAML.parse """
+        str:
+            "string with \\n inside"
+        """
+        .toEqual YAML.parse YAML.dump str: 'string with \n inside'
+
+
+    it 'can be double-quoted strings with line breaks and backslashes', ->
+
+        expect YAML.parse """
+        str:
+            "string with \\n inside and \\\\ also"
+        """
+        .toEqual YAML.parse YAML.dump str: 'string with \n inside and \\ also'
+
+
+    it 'can be single-quoted strings with line breaks and backslashes', ->
+
+        expect YAML.parse """
+        str:
+            'string with \\n inside and \\\\ also'
+        """
+        .toEqual YAML.parse YAML.dump str: 'string with \\n inside and \\\\ also'
+
+
+    it 'can be single-quoted strings with escaped line breaks', ->
+
+        expect YAML.parse """
+        str:
+            'string with \\n inside'
+        """
+        .toEqual YAML.parse YAML.dump str: 'string with \\n inside'
+
+
+    it 'can have string characters in sequences', ->
+
+        expect YAML.parse """
+        - What's Yaml?
+        - It's for writing data structures in plain text.
+        - And?
+        - And what? That's not good enough for you?
+        - No, I mean, "And what about Yaml?"
+        - Oh, oh yeah. Uh.. Yaml for JavaScript.
+        """
+        .toEqual YAML.parse YAML.dump [
+            "What's Yaml?",
+            "It's for writing data structures in plain text.",
+            "And?",
+            "And what? That's not good enough for you?",
+            "No, I mean, \"And what about Yaml?\"",
+            "Oh, oh yeah. Uh.. Yaml for JavaScript."
+        ]
+
+
+    it 'can have indicators in strings', ->
+
+        expect YAML.parse """
+        the colon followed by space is an indicator: but is a string:right here
+        same for the pound sign: here we have it#in a string
+        the comma can, honestly, be used in most cases: [ but not in, inline collections ]
+        """
+        .toEqual YAML.parse YAML.dump (
+            'the colon followed by space is an indicator': 'but is a string:right here',
+            'same for the pound sign': 'here we have it#in a string',
+            'the comma can, honestly, be used in most cases': ['but not in', 'inline collections']
+        )
+
+
+    it 'can force strings', ->
+
+        expect YAML.parse """
+        date string: !str 2001-08-01
+        number string: !str 192
+        date string 2: !!str 2001-08-01
+        number string 2: !!str 192
+        """
+        .toEqual YAML.parse YAML.dump (
+            'date string': '2001-08-01',
+            'number string': '192' ,
+            'date string 2': '2001-08-01',
+            'number string 2': '192'
+        )
+
+
+    it 'can be single-quoted strings', ->
+
+        expect YAML.parse """
+        all my favorite symbols: '#:!/%.)'
+        a few i hate: '&(*'
+        why do i hate them?: 'it''s very hard to explain'
+        """
+        .toEqual YAML.parse YAML.dump (
+            'all my favorite symbols': '#:!/%.)',
+            'a few i hate': '&(*',
+            'why do i hate them?': 'it\'s very hard to explain'
+        )
+
+
+    it 'can be double-quoted strings', ->
+
+        expect YAML.parse """
+        i know where i want my line breaks: "one here\\nand another here\\n"
+        """
+        .toEqual YAML.parse YAML.dump (
+            'i know where i want my line breaks': "one here\nand another here\n"
+        )
+
+
+    it 'can be null', ->
+
+        expect YAML.parse """
+        name: Mr. Show
+        hosted by: Bob and David
+        date of next season: ~
+        """
+        .toEqual YAML.parse YAML.dump (
+            'name': 'Mr. Show'
+            'hosted by': 'Bob and David'
+            'date of next season': null
+        )
+
+
+    it 'can be boolean', ->
+
+        expect YAML.parse """
+        Is Gus a Liar?: true
+        Do I rely on Gus for Sustenance?: false
+        """
+        .toEqual YAML.parse YAML.dump (
+            'Is Gus a Liar?': true
+            'Do I rely on Gus for Sustenance?': false
+        )
+
+
+    it 'can be integers', ->
+
+        expect YAML.parse """
+        zero: 0
+        simple: 12
+        one-thousand: 1,000
+        negative one-thousand: -1,000
+        """
+        .toEqual YAML.parse YAML.dump (
+            'zero': 0
+            'simple': 12
+            'one-thousand': 1000
+            'negative one-thousand': -1000
+        )
+
+
+    it 'can be integers as map keys', ->
+
+        expect YAML.parse """
+        1: one
+        2: two
+        3: three
+        """
+        .toEqual YAML.parse YAML.dump (
+            1: 'one'
+            2: 'two'
+            3: 'three'
+        )
+
+
+    it 'can be floats', ->
+
+        expect YAML.parse """
+        a simple float: 2.00
+        larger float: 1,000.09
+        scientific notation: 1.00009e+3
+        """
+        .toEqual YAML.parse YAML.dump (
+            'a simple float': 2.0
+            'larger float': 1000.09
+            'scientific notation': 1000.09
+        )
+
+
+    it 'can be time', ->
+
+        iso8601Date = new Date Date.UTC(2001, 12-1, 14, 21, 59, 43, 10)
+        iso8601Date.setTime iso8601Date.getTime() - 5 * 3600 * 1000
+
+        spaceSeparatedDate = new Date Date.UTC(2001, 12-1, 14, 21, 59, 43, 10)
+        spaceSeparatedDate.setTime spaceSeparatedDate.getTime() - 5 * 3600 * 1000
+
+        withDatesToTime = (input) ->
+            res = {}
+            for key, val of input
+                res[key] = Math.round(val.getTime() / 1000) * 1000
+            return res
+
+        expect withDatesToTime(YAML.parse """
+            iso8601: 2001-12-14t21:59:43.10-05:00
+            space seperated: 2001-12-14 21:59:43.10 -05:00
+        """)
+        .toEqual YAML.parse YAML.dump withDatesToTime (
+            'iso8601': iso8601Date
+            'space seperated': spaceSeparatedDate
+        )
+
+
+    it 'can be date', ->
+
+        aDate = new Date Date.UTC(1976, 7-1, 31, 0, 0, 0, 0)
+
+        withDatesToTime = (input) ->
+            return input
+            res = {}
+            for key, val of input
+                res[key] = Math.round(val.getTime() / 1000) * 1000
+            return res
+
+        expect withDatesToTime(YAML.parse """
+            date: 1976-07-31
+        """)
+        .toEqual YAML.parse YAML.dump withDatesToTime (
+            'date': aDate
+        )
+
+
+
+describe 'Dumped YAML Blocks', ->
+
+    it 'can be single ending newline', ->
+
+        expect YAML.parse """
+        ---
+        this: |
+            Foo
+            Bar
+        """
+        .toEqual YAML.parse YAML.dump 'this': "Foo\nBar\n"
+
+
+    it 'can be single ending newline with \'+\' indicator', ->
+
+        expect YAML.parse """
+        normal: |
+          extra new lines not kept
+
+        preserving: |+
+          extra new lines are kept
+
+
+        dummy: value
+        """
+        .toEqual YAML.parse YAML.dump (
+            'normal': "extra new lines not kept\n"
+            'preserving': "extra new lines are kept\n\n\n"
+            'dummy': 'value'
+        )
+
+
+    it 'can be multi-line block handling trailing newlines in function of \'+\', \'-\' indicators', ->
+
+        expect YAML.parse """
+        clipped: |
+            This has one newline.
+
+
+
+        same as "clipped" above: "This has one newline.\\n"
+
+        stripped: |-
+            This has no newline.
+
+
+
+        same as "stripped" above: "This has no newline."
+
+        kept: |+
+            This has four newlines.
+
+
+
+        same as "kept" above: "This has four newlines.\\n\\n\\n\\n"
+        """
+        .toEqual YAML.parse YAML.dump (
+            'clipped': "This has one newline.\n"
+            'same as "clipped" above': "This has one newline.\n"
+            'stripped':'This has no newline.'
+            'same as "stripped" above': 'This has no newline.'
+            'kept': "This has four newlines.\n\n\n\n"
+            'same as "kept" above': "This has four newlines.\n\n\n\n"
+        )
+
+
+    it 'can be folded block in a sequence', ->
+
+        expect YAML.parse """
+        ---
+        - apple
+        - banana
+        - >
+            can't you see
+            the beauty of yaml?
+            hmm
+        - dog
+        """
+        .toEqual YAML.parse YAML.dump [
+            'apple',
+            'banana',
+            "can't you see the beauty of yaml? hmm\n",
+            'dog'
+        ]
+
+
+    it 'can be folded block as a mapping value', ->
+
+        expect YAML.parse """
+        ---
+        quote: >
+            Mark McGwire's
+            year was crippled
+            by a knee injury.
+        source: espn
+        """
+        .toEqual YAML.parse YAML.dump (
+            'quote': "Mark McGwire's year was crippled by a knee injury.\n"
+            'source': 'espn'
+        )
+
+
+    it 'can be folded block handling trailing newlines in function of \'+\', \'-\' indicators', ->
+
+        expect YAML.parse """
+        clipped: >
+            This has one newline.
+
+
+
+        same as "clipped" above: "This has one newline.\\n"
+
+        stripped: >-
+            This has no newline.
+
+
+
+        same as "stripped" above: "This has no newline."
+
+        kept: >+
+            This has four newlines.
+
+
+
+        same as "kept" above: "This has four newlines.\\n\\n\\n\\n"
+        """
+        .toEqual YAML.parse YAML.dump (
+            'clipped': "This has one newline.\n"
+            'same as "clipped" above': "This has one newline.\n"
+            'stripped': 'This has no newline.'
+            'same as "stripped" above': 'This has no newline.'
+            'kept': "This has four newlines.\n\n\n\n"
+            'same as "kept" above': "This has four newlines.\n\n\n\n"
+        )
+
+
+
+describe 'Dumped YAML Comments', ->
+
+    it 'can begin the document', ->
+
+        expect YAML.parse """
+        # This is a comment
+        hello: world
+        """
+        .toEqual YAML.parse YAML.dump (
+            hello: 'world'
+        )
+
+
+    it 'can finish a line', ->
+
+        expect YAML.parse """
+        hello: world # This is a comment
+        """
+        .toEqual YAML.parse YAML.dump (
+            hello: 'world'
+        )
+
+
+    it 'can end the document', ->
+
+        expect YAML.parse """
+        hello: world
+        # This is a comment
+        """
+        .toEqual YAML.parse YAML.dump (
+            hello: 'world'
+        )
+
+
+
+describe 'Dumped YAML Aliases and Anchors', ->
+
+    it 'can be simple alias', ->
+
+        expect YAML.parse """
+        - &showell Steve
+        - Clark
+        - Brian
+        - Oren
+        - *showell
+        """
+        .toEqual YAML.parse YAML.dump ['Steve', 'Clark', 'Brian', 'Oren', 'Steve']
+
+
+    it 'can be alias of a mapping', ->
+
+        expect YAML.parse """
+        - &hello
+            Meat: pork
+            Starch: potato
+        - banana
+        - *hello
+        """
+        .toEqual YAML.parse YAML.dump [
+            Meat: 'pork', Starch: 'potato'
+        ,
+            'banana'
+        ,
+            Meat: 'pork', Starch: 'potato'
+        ]
+
+
+
+describe 'Dumped YAML Documents', ->
+
+    it 'can have YAML header', ->
+
+        expect YAML.parse """
+        --- %YAML:1.0
+        foo: 1
+        bar: 2
+        """
+        .toEqual YAML.parse YAML.dump (
+            foo: 1
+            bar: 2
+        )
+
+
+    it 'can have leading document separator', ->
+
+        expect YAML.parse """
+        ---
+        - foo: 1
+          bar: 2
+        """
+        .toEqual YAML.parse YAML.dump [(
+            foo: 1
+            bar: 2
+        )]
+
+
+    it 'can have multiple document separators in block', ->
+
+        expect YAML.parse """
+        foo: |
+            ---
+            foo: bar
+            ---
+            yo: baz
+        bar: |
+            fooness
+        """
+        .toEqual YAML.parse YAML.dump (
+           foo: "---\nfoo: bar\n---\nyo: baz\n"
+           bar: "fooness\n"
+        )
+
+
+# Loading
+# (disable test when running locally from file)
+#
+url = document?.location?.href
+if not(url?) or url.indexOf('file://') is -1
+
+    examplePath = 'spec/example.yml'
+    if __dirname?
+        examplePath = __dirname+'/example.yml'
+
+    describe 'YAML loading', ->
+
+        it 'can be done synchronously', ->
+
+            expect(YAML.load(examplePath)).toEqual (
+                this: 'is'
+                a: ['YAML', 'example']
+            )
+
+
+        it 'can be done asynchronously', (done) ->
+
+            YAML.load examplePath, (result) ->
+
+                expect(result).toEqual (
+                    this: 'is'
+                    a: ['YAML', 'example']
+                )
+
+                done()
diff --git a/src/main/webapp/scripts/lib/yamljs/test/spec/YamlSpec.js b/src/main/webapp/scripts/lib/yamljs/test/spec/YamlSpec.js
new file mode 100755
index 0000000000000000000000000000000000000000..feac4145a66c8fd2760029a30b2cbad880b13c91
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/test/spec/YamlSpec.js
@@ -0,0 +1,757 @@
+// Generated by CoffeeScript 1.10.0
+var YAML, examplePath, ref, url;
+
+if (typeof YAML === "undefined" || YAML === null) {
+  YAML = require('../../src/Yaml');
+}
+
+describe('Parsed YAML Collections', function() {
+  it('can be simple sequence', function() {
+    return expect(YAML.parse("- apple\n- banana\n- carrot")).toEqual(['apple', 'banana', 'carrot']);
+  });
+  it('can be nested sequences', function() {
+    return expect(YAML.parse("-\n - foo\n - bar\n - baz")).toEqual([['foo', 'bar', 'baz']]);
+  });
+  it('can be mixed sequences', function() {
+    return expect(YAML.parse("- apple\n-\n - foo\n - bar\n - x123\n- banana\n- carrot")).toEqual(['apple', ['foo', 'bar', 'x123'], 'banana', 'carrot']);
+  });
+  it('can be deeply nested sequences', function() {
+    return expect(YAML.parse("-\n -\n  - uno\n  - dos")).toEqual([[['uno', 'dos']]]);
+  });
+  it('can be simple mapping', function() {
+    return expect(YAML.parse("foo: whatever\nbar: stuff")).toEqual({
+      foo: 'whatever',
+      bar: 'stuff'
+    });
+  });
+  it('can be sequence in a mapping', function() {
+    return expect(YAML.parse("foo: whatever\nbar:\n - uno\n - dos")).toEqual({
+      foo: 'whatever',
+      bar: ['uno', 'dos']
+    });
+  });
+  it('can be nested mappings', function() {
+    return expect(YAML.parse("foo: whatever\nbar:\n fruit: apple\n name: steve\n sport: baseball")).toEqual({
+      foo: 'whatever',
+      bar: {
+        fruit: 'apple',
+        name: 'steve',
+        sport: 'baseball'
+      }
+    });
+  });
+  it('can be mixed mapping', function() {
+    return expect(YAML.parse("foo: whatever\nbar:\n -\n   fruit: apple\n   name: steve\n   sport: baseball\n - more\n -\n   python: rocks\n   perl: papers\n   ruby: scissorses")).toEqual({
+      foo: 'whatever',
+      bar: [
+        {
+          fruit: 'apple',
+          name: 'steve',
+          sport: 'baseball'
+        }, 'more', {
+          python: 'rocks',
+          perl: 'papers',
+          ruby: 'scissorses'
+        }
+      ]
+    });
+  });
+  it('can have mapping-in-sequence shortcut', function() {
+    return expect(YAML.parse("- work on YAML.py:\n   - work on Store")).toEqual([
+      {
+        'work on YAML.py': ['work on Store']
+      }
+    ]);
+  });
+  it('can have unindented sequence-in-mapping shortcut', function() {
+    return expect(YAML.parse("allow:\n- 'localhost'\n- '%.sourceforge.net'\n- '%.freepan.org'")).toEqual({
+      allow: ['localhost', '%.sourceforge.net', '%.freepan.org']
+    });
+  });
+  it('can merge key', function() {
+    return expect(YAML.parse("mapping:\n  name: Joe\n  job: Accountant\n  <<:\n    age: 38")).toEqual({
+      mapping: {
+        name: 'Joe',
+        job: 'Accountant',
+        age: 38
+      }
+    });
+  });
+  return it('can ignore trailing empty lines for smallest indent', function() {
+    return expect(YAML.parse(" trailing: empty lines\n")).toEqual({
+      trailing: 'empty lines'
+    });
+  });
+});
+
+describe('Parsed YAML Inline Collections', function() {
+  it('can be simple inline array', function() {
+    return expect(YAML.parse("---\nseq: [ a, b, c ]")).toEqual({
+      seq: ['a', 'b', 'c']
+    });
+  });
+  it('can be simple inline hash', function() {
+    return expect(YAML.parse("---\nhash: { name: Steve, foo: bar }")).toEqual({
+      hash: {
+        name: 'Steve',
+        foo: 'bar'
+      }
+    });
+  });
+  it('can be nested inline hash', function() {
+    return expect(YAML.parse("---\nhash: { val1: \"string\", val2: { v2k1: \"v2k1v\" } }")).toEqual({
+      hash: {
+        val1: 'string',
+        val2: {
+          v2k1: 'v2k1v'
+        }
+      }
+    });
+  });
+  return it('can be multi-line inline collections', function() {
+    return expect(YAML.parse("languages: [ Ruby,\n             Perl,\n             Python ]\nwebsites: { YAML: yaml.org,\n            Ruby: ruby-lang.org,\n            Python: python.org,\n            Perl: use.perl.org }")).toEqual({
+      languages: ['Ruby', 'Perl', 'Python'],
+      websites: {
+        YAML: 'yaml.org',
+        Ruby: 'ruby-lang.org',
+        Python: 'python.org',
+        Perl: 'use.perl.org'
+      }
+    });
+  });
+});
+
+describe('Parsed YAML Basic Types', function() {
+  it('can be strings', function() {
+    return expect(YAML.parse("---\nString")).toEqual('String');
+  });
+  it('can be double-quoted strings with backslashes', function() {
+    return expect(YAML.parse("str:\n    \"string with \\\\ inside\"")).toEqual({
+      str: 'string with \\ inside'
+    });
+  });
+  it('can be single-quoted strings with backslashes', function() {
+    return expect(YAML.parse("str:\n    'string with \\\\ inside'")).toEqual({
+      str: 'string with \\\\ inside'
+    });
+  });
+  it('can be double-quoted strings with line breaks', function() {
+    return expect(YAML.parse("str:\n    \"string with \\n inside\"")).toEqual({
+      str: 'string with \n inside'
+    });
+  });
+  it('can be single-quoted strings with escaped line breaks', function() {
+    return expect(YAML.parse("str:\n    'string with \\n inside'")).toEqual({
+      str: 'string with \\n inside'
+    });
+  });
+  it('can be double-quoted strings with line breaks and backslashes', function() {
+    return expect(YAML.parse("str:\n    \"string with \\n inside and \\\\ also\"")).toEqual({
+      str: 'string with \n inside and \\ also'
+    });
+  });
+  it('can be single-quoted strings with line breaks and backslashes', function() {
+    return expect(YAML.parse("str:\n    'string with \\n inside and \\\\ also'")).toEqual({
+      str: 'string with \\n inside and \\\\ also'
+    });
+  });
+  it('can have string characters in sequences', function() {
+    return expect(YAML.parse("- What's Yaml?\n- It's for writing data structures in plain text.\n- And?\n- And what? That's not good enough for you?\n- No, I mean, \"And what about Yaml?\"\n- Oh, oh yeah. Uh.. Yaml for JavaScript.")).toEqual(["What's Yaml?", "It's for writing data structures in plain text.", "And?", "And what? That's not good enough for you?", "No, I mean, \"And what about Yaml?\"", "Oh, oh yeah. Uh.. Yaml for JavaScript."]);
+  });
+  it('can have indicators in strings', function() {
+    return expect(YAML.parse("the colon followed by space is an indicator: but is a string:right here\nsame for the pound sign: here we have it#in a string\nthe comma can, honestly, be used in most cases: [ but not in, inline collections ]")).toEqual({
+      'the colon followed by space is an indicator': 'but is a string:right here',
+      'same for the pound sign': 'here we have it#in a string',
+      'the comma can, honestly, be used in most cases': ['but not in', 'inline collections']
+    });
+  });
+  it('can force strings', function() {
+    return expect(YAML.parse("date string: !str 2001-08-01\nnumber string: !str 192\ndate string 2: !!str 2001-08-01\nnumber string 2: !!str 192")).toEqual({
+      'date string': '2001-08-01',
+      'number string': '192',
+      'date string 2': '2001-08-01',
+      'number string 2': '192'
+    });
+  });
+  it('can be single-quoted strings', function() {
+    return expect(YAML.parse("all my favorite symbols: '#:!/%.)'\na few i hate: '&(*'\nwhy do i hate them?: 'it''s very hard to explain'")).toEqual({
+      'all my favorite symbols': '#:!/%.)',
+      'a few i hate': '&(*',
+      'why do i hate them?': 'it\'s very hard to explain'
+    });
+  });
+  it('can be double-quoted strings', function() {
+    return expect(YAML.parse("i know where i want my line breaks: \"one here\\nand another here\\n\"")).toEqual({
+      'i know where i want my line breaks': "one here\nand another here\n"
+    });
+  });
+  it('can be null', function() {
+    return expect(YAML.parse("name: Mr. Show\nhosted by: Bob and David\ndate of next season: ~")).toEqual({
+      'name': 'Mr. Show',
+      'hosted by': 'Bob and David',
+      'date of next season': null
+    });
+  });
+  it('can be boolean', function() {
+    return expect(YAML.parse("Is Gus a Liar?: true\nDo I rely on Gus for Sustenance?: false")).toEqual({
+      'Is Gus a Liar?': true,
+      'Do I rely on Gus for Sustenance?': false
+    });
+  });
+  it('can be integers', function() {
+    return expect(YAML.parse("zero: 0\nsimple: 12\none-thousand: 1,000\nnegative one-thousand: -1,000")).toEqual({
+      'zero': 0,
+      'simple': 12,
+      'one-thousand': 1000,
+      'negative one-thousand': -1000
+    });
+  });
+  it('can be integers as map keys', function() {
+    return expect(YAML.parse("1: one\n2: two\n3: three")).toEqual({
+      1: 'one',
+      2: 'two',
+      3: 'three'
+    });
+  });
+  it('can be floats', function() {
+    return expect(YAML.parse("a simple float: 2.00\nlarger float: 1,000.09\nscientific notation: 1.00009e+3")).toEqual({
+      'a simple float': 2.0,
+      'larger float': 1000.09,
+      'scientific notation': 1000.09
+    });
+  });
+  it('can be time', function() {
+    var iso8601Date, spaceSeparatedDate, withDatesToTime;
+    iso8601Date = new Date(Date.UTC(2001, 12 - 1, 14, 21, 59, 43, 10));
+    iso8601Date.setTime(iso8601Date.getTime() - 5 * 3600 * 1000);
+    spaceSeparatedDate = new Date(Date.UTC(2001, 12 - 1, 14, 21, 59, 43, 10));
+    spaceSeparatedDate.setTime(spaceSeparatedDate.getTime() - 5 * 3600 * 1000);
+    withDatesToTime = function(input) {
+      var key, res, val;
+      res = {};
+      for (key in input) {
+        val = input[key];
+        res[key] = Math.round(val.getTime() / 1000) * 1000;
+      }
+      return res;
+    };
+    return expect(withDatesToTime(YAML.parse("iso8601: 2001-12-14t21:59:43.10-05:00\nspace seperated: 2001-12-14 21:59:43.10 -05:00"))).toEqual(withDatesToTime({
+      'iso8601': iso8601Date,
+      'space seperated': spaceSeparatedDate
+    }));
+  });
+  return it('can be date', function() {
+    var aDate, withDatesToTime;
+    aDate = new Date(Date.UTC(1976, 7 - 1, 31, 0, 0, 0, 0));
+    withDatesToTime = function(input) {
+      var key, res, val;
+      return input;
+      res = {};
+      for (key in input) {
+        val = input[key];
+        res[key] = Math.round(val.getTime() / 1000) * 1000;
+      }
+      return res;
+    };
+    return expect(withDatesToTime(YAML.parse("date: 1976-07-31"))).toEqual(withDatesToTime({
+      'date': aDate
+    }));
+  });
+});
+
+describe('Parsed YAML Blocks', function() {
+  it('can be single ending newline', function() {
+    return expect(YAML.parse("---\nthis: |\n    Foo\n    Bar")).toEqual({
+      'this': "Foo\nBar\n"
+    });
+  });
+  it('can be single ending newline with \'+\' indicator', function() {
+    return expect(YAML.parse("normal: |\n  extra new lines not kept\n\npreserving: |+\n  extra new lines are kept\n\n\ndummy: value")).toEqual({
+      'normal': "extra new lines not kept\n",
+      'preserving': "extra new lines are kept\n\n\n",
+      'dummy': 'value'
+    });
+  });
+  it('can be multi-line block handling trailing newlines in function of \'+\', \'-\' indicators', function() {
+    return expect(YAML.parse("clipped: |\n    This has one newline.\n\n\n\nsame as \"clipped\" above: \"This has one newline.\\n\"\n\nstripped: |-\n    This has no newline.\n\n\n\nsame as \"stripped\" above: \"This has no newline.\"\n\nkept: |+\n    This has four newlines.\n\n\n\nsame as \"kept\" above: \"This has four newlines.\\n\\n\\n\\n\"")).toEqual({
+      'clipped': "This has one newline.\n",
+      'same as "clipped" above': "This has one newline.\n",
+      'stripped': 'This has no newline.',
+      'same as "stripped" above': 'This has no newline.',
+      'kept': "This has four newlines.\n\n\n\n",
+      'same as "kept" above': "This has four newlines.\n\n\n\n"
+    });
+  });
+  it('can be folded block in a sequence', function() {
+    return expect(YAML.parse("---\n- apple\n- banana\n- >\n    can't you see\n    the beauty of yaml?\n    hmm\n- dog")).toEqual(['apple', 'banana', "can't you see the beauty of yaml? hmm\n", 'dog']);
+  });
+  it('can be folded block as a mapping value', function() {
+    return expect(YAML.parse("---\nquote: >\n    Mark McGwire's\n    year was crippled\n    by a knee injury.\nsource: espn")).toEqual({
+      'quote': "Mark McGwire's year was crippled by a knee injury.\n",
+      'source': 'espn'
+    });
+  });
+  it('can be folded block handling trailing newlines in function of \'+\', \'-\' indicators', function() {
+    return expect(YAML.parse("clipped: >\n    This has one newline.\n\n\n\nsame as \"clipped\" above: \"This has one newline.\\n\"\n\nstripped: >-\n    This has no newline.\n\n\n\nsame as \"stripped\" above: \"This has no newline.\"\n\nkept: >+\n    This has four newlines.\n\n\n\nsame as \"kept\" above: \"This has four newlines.\\n\\n\\n\\n\"")).toEqual({
+      'clipped': "This has one newline.\n",
+      'same as "clipped" above': "This has one newline.\n",
+      'stripped': 'This has no newline.',
+      'same as "stripped" above': 'This has no newline.',
+      'kept': "This has four newlines.\n\n\n\n",
+      'same as "kept" above': "This has four newlines.\n\n\n\n"
+    });
+  });
+  return it('can be the whole document as intented block', function() {
+    return expect(YAML.parse("---\n  foo: \"bar\"\n  baz:\n    - \"qux\"\n    - \"quxx\"\n  corge: null")).toEqual({
+      'foo': "bar",
+      'baz': ['qux', 'quxx'],
+      'corge': null
+    });
+  });
+});
+
+describe('Parsed YAML Comments', function() {
+  it('can begin the document', function() {
+    return expect(YAML.parse("# This is a comment\nhello: world")).toEqual({
+      hello: 'world'
+    });
+  });
+  it('can be less indented in mapping', function() {
+    return expect(YAML.parse("parts:\n    a: 'b'\n    # normally indented comment\n    c: 'd'\n# less indented comment\n    e: 'f'")).toEqual({
+      parts: {
+        a: 'b',
+        c: 'd',
+        e: 'f'
+      }
+    });
+  });
+  it('can be less indented in sequence', function() {
+    return expect(YAML.parse("list-header:\n  - item1\n#  - item2\n  - item3\n  # - item4")).toEqual({
+      'list-header': ['item1', 'item3']
+    });
+  });
+  it('can finish a line', function() {
+    return expect(YAML.parse("hello: world # This is a comment")).toEqual({
+      hello: 'world'
+    });
+  });
+  return it('can end the document', function() {
+    return expect(YAML.parse("hello: world\n# This is a comment")).toEqual({
+      hello: 'world'
+    });
+  });
+});
+
+describe('Parsed YAML Aliases and Anchors', function() {
+  it('can be simple alias', function() {
+    return expect(YAML.parse("- &showell Steve\n- Clark\n- Brian\n- Oren\n- *showell")).toEqual(['Steve', 'Clark', 'Brian', 'Oren', 'Steve']);
+  });
+  return it('can be alias of a mapping', function() {
+    return expect(YAML.parse("- &hello\n    Meat: pork\n    Starch: potato\n- banana\n- *hello")).toEqual([
+      {
+        Meat: 'pork',
+        Starch: 'potato'
+      }, 'banana', {
+        Meat: 'pork',
+        Starch: 'potato'
+      }
+    ]);
+  });
+});
+
+describe('Parsed YAML Documents', function() {
+  it('can have YAML header', function() {
+    return expect(YAML.parse("--- %YAML:1.0\nfoo: 1\nbar: 2")).toEqual({
+      foo: 1,
+      bar: 2
+    });
+  });
+  it('can have leading document separator', function() {
+    return expect(YAML.parse("---\n- foo: 1\n  bar: 2")).toEqual([
+      {
+        foo: 1,
+        bar: 2
+      }
+    ]);
+  });
+  return it('can have multiple document separators in block', function() {
+    return expect(YAML.parse("foo: |\n    ---\n    foo: bar\n    ---\n    yo: baz\nbar: |\n    fooness")).toEqual({
+      foo: "---\nfoo: bar\n---\nyo: baz\n",
+      bar: "fooness\n"
+    });
+  });
+});
+
+describe('Dumped YAML Collections', function() {
+  it('can be simple sequence', function() {
+    return expect(YAML.parse("- apple\n- banana\n- carrot")).toEqual(YAML.parse(YAML.dump(['apple', 'banana', 'carrot'])));
+  });
+  it('can be nested sequences', function() {
+    return expect(YAML.parse("-\n - foo\n - bar\n - baz")).toEqual(YAML.parse(YAML.dump([['foo', 'bar', 'baz']])));
+  });
+  it('can be mixed sequences', function() {
+    return expect(YAML.parse("- apple\n-\n - foo\n - bar\n - x123\n- banana\n- carrot")).toEqual(YAML.parse(YAML.dump(['apple', ['foo', 'bar', 'x123'], 'banana', 'carrot'])));
+  });
+  it('can be deeply nested sequences', function() {
+    return expect(YAML.parse("-\n -\n  - uno\n  - dos")).toEqual(YAML.parse(YAML.dump([[['uno', 'dos']]])));
+  });
+  it('can be simple mapping', function() {
+    return expect(YAML.parse("foo: whatever\nbar: stuff")).toEqual(YAML.parse(YAML.dump({
+      foo: 'whatever',
+      bar: 'stuff'
+    })));
+  });
+  it('can be sequence in a mapping', function() {
+    return expect(YAML.parse("foo: whatever\nbar:\n - uno\n - dos")).toEqual(YAML.parse(YAML.dump({
+      foo: 'whatever',
+      bar: ['uno', 'dos']
+    })));
+  });
+  it('can be nested mappings', function() {
+    return expect(YAML.parse("foo: whatever\nbar:\n fruit: apple\n name: steve\n sport: baseball")).toEqual(YAML.parse(YAML.dump({
+      foo: 'whatever',
+      bar: {
+        fruit: 'apple',
+        name: 'steve',
+        sport: 'baseball'
+      }
+    })));
+  });
+  it('can be mixed mapping', function() {
+    return expect(YAML.parse("foo: whatever\nbar:\n -\n   fruit: apple\n   name: steve\n   sport: baseball\n - more\n -\n   python: rocks\n   perl: papers\n   ruby: scissorses")).toEqual(YAML.parse(YAML.dump({
+      foo: 'whatever',
+      bar: [
+        {
+          fruit: 'apple',
+          name: 'steve',
+          sport: 'baseball'
+        }, 'more', {
+          python: 'rocks',
+          perl: 'papers',
+          ruby: 'scissorses'
+        }
+      ]
+    })));
+  });
+  it('can have mapping-in-sequence shortcut', function() {
+    return expect(YAML.parse("- work on YAML.py:\n   - work on Store")).toEqual(YAML.parse(YAML.dump([
+      {
+        'work on YAML.py': ['work on Store']
+      }
+    ])));
+  });
+  it('can have unindented sequence-in-mapping shortcut', function() {
+    return expect(YAML.parse("allow:\n- 'localhost'\n- '%.sourceforge.net'\n- '%.freepan.org'")).toEqual(YAML.parse(YAML.dump({
+      allow: ['localhost', '%.sourceforge.net', '%.freepan.org']
+    })));
+  });
+  return it('can merge key', function() {
+    return expect(YAML.parse("mapping:\n  name: Joe\n  job: Accountant\n  <<:\n    age: 38")).toEqual(YAML.parse(YAML.dump({
+      mapping: {
+        name: 'Joe',
+        job: 'Accountant',
+        age: 38
+      }
+    })));
+  });
+});
+
+describe('Dumped YAML Inline Collections', function() {
+  it('can be simple inline array', function() {
+    return expect(YAML.parse("---\nseq: [ a, b, c ]")).toEqual(YAML.parse(YAML.dump({
+      seq: ['a', 'b', 'c']
+    })));
+  });
+  it('can be simple inline hash', function() {
+    return expect(YAML.parse("---\nhash: { name: Steve, foo: bar }")).toEqual(YAML.parse(YAML.dump({
+      hash: {
+        name: 'Steve',
+        foo: 'bar'
+      }
+    })));
+  });
+  it('can be multi-line inline collections', function() {
+    return expect(YAML.parse("languages: [ Ruby,\n             Perl,\n             Python ]\nwebsites: { YAML: yaml.org,\n            Ruby: ruby-lang.org,\n            Python: python.org,\n            Perl: use.perl.org }")).toEqual(YAML.parse(YAML.dump({
+      languages: ['Ruby', 'Perl', 'Python'],
+      websites: {
+        YAML: 'yaml.org',
+        Ruby: 'ruby-lang.org',
+        Python: 'python.org',
+        Perl: 'use.perl.org'
+      }
+    })));
+  });
+  return it('can be dumped empty sequences in mappings', function() {
+    return expect(YAML.parse(YAML.dump({
+      key: []
+    }))).toEqual({
+      key: []
+    });
+  });
+});
+
+describe('Dumped YAML Basic Types', function() {
+  it('can be strings', function() {
+    return expect(YAML.parse("---\nString")).toEqual(YAML.parse(YAML.dump('String')));
+  });
+  it('can be double-quoted strings with backslashes', function() {
+    return expect(YAML.parse("str:\n    \"string with \\\\ inside\"")).toEqual(YAML.parse(YAML.dump({
+      str: 'string with \\ inside'
+    })));
+  });
+  it('can be single-quoted strings with backslashes', function() {
+    return expect(YAML.parse("str:\n    'string with \\\\ inside'")).toEqual(YAML.parse(YAML.dump({
+      str: 'string with \\\\ inside'
+    })));
+  });
+  it('can be double-quoted strings with line breaks', function() {
+    return expect(YAML.parse("str:\n    \"string with \\n inside\"")).toEqual(YAML.parse(YAML.dump({
+      str: 'string with \n inside'
+    })));
+  });
+  it('can be double-quoted strings with line breaks and backslashes', function() {
+    return expect(YAML.parse("str:\n    \"string with \\n inside and \\\\ also\"")).toEqual(YAML.parse(YAML.dump({
+      str: 'string with \n inside and \\ also'
+    })));
+  });
+  it('can be single-quoted strings with line breaks and backslashes', function() {
+    return expect(YAML.parse("str:\n    'string with \\n inside and \\\\ also'")).toEqual(YAML.parse(YAML.dump({
+      str: 'string with \\n inside and \\\\ also'
+    })));
+  });
+  it('can be single-quoted strings with escaped line breaks', function() {
+    return expect(YAML.parse("str:\n    'string with \\n inside'")).toEqual(YAML.parse(YAML.dump({
+      str: 'string with \\n inside'
+    })));
+  });
+  it('can have string characters in sequences', function() {
+    return expect(YAML.parse("- What's Yaml?\n- It's for writing data structures in plain text.\n- And?\n- And what? That's not good enough for you?\n- No, I mean, \"And what about Yaml?\"\n- Oh, oh yeah. Uh.. Yaml for JavaScript.")).toEqual(YAML.parse(YAML.dump(["What's Yaml?", "It's for writing data structures in plain text.", "And?", "And what? That's not good enough for you?", "No, I mean, \"And what about Yaml?\"", "Oh, oh yeah. Uh.. Yaml for JavaScript."])));
+  });
+  it('can have indicators in strings', function() {
+    return expect(YAML.parse("the colon followed by space is an indicator: but is a string:right here\nsame for the pound sign: here we have it#in a string\nthe comma can, honestly, be used in most cases: [ but not in, inline collections ]")).toEqual(YAML.parse(YAML.dump({
+      'the colon followed by space is an indicator': 'but is a string:right here',
+      'same for the pound sign': 'here we have it#in a string',
+      'the comma can, honestly, be used in most cases': ['but not in', 'inline collections']
+    })));
+  });
+  it('can force strings', function() {
+    return expect(YAML.parse("date string: !str 2001-08-01\nnumber string: !str 192\ndate string 2: !!str 2001-08-01\nnumber string 2: !!str 192")).toEqual(YAML.parse(YAML.dump({
+      'date string': '2001-08-01',
+      'number string': '192',
+      'date string 2': '2001-08-01',
+      'number string 2': '192'
+    })));
+  });
+  it('can be single-quoted strings', function() {
+    return expect(YAML.parse("all my favorite symbols: '#:!/%.)'\na few i hate: '&(*'\nwhy do i hate them?: 'it''s very hard to explain'")).toEqual(YAML.parse(YAML.dump({
+      'all my favorite symbols': '#:!/%.)',
+      'a few i hate': '&(*',
+      'why do i hate them?': 'it\'s very hard to explain'
+    })));
+  });
+  it('can be double-quoted strings', function() {
+    return expect(YAML.parse("i know where i want my line breaks: \"one here\\nand another here\\n\"")).toEqual(YAML.parse(YAML.dump({
+      'i know where i want my line breaks': "one here\nand another here\n"
+    })));
+  });
+  it('can be null', function() {
+    return expect(YAML.parse("name: Mr. Show\nhosted by: Bob and David\ndate of next season: ~")).toEqual(YAML.parse(YAML.dump({
+      'name': 'Mr. Show',
+      'hosted by': 'Bob and David',
+      'date of next season': null
+    })));
+  });
+  it('can be boolean', function() {
+    return expect(YAML.parse("Is Gus a Liar?: true\nDo I rely on Gus for Sustenance?: false")).toEqual(YAML.parse(YAML.dump({
+      'Is Gus a Liar?': true,
+      'Do I rely on Gus for Sustenance?': false
+    })));
+  });
+  it('can be integers', function() {
+    return expect(YAML.parse("zero: 0\nsimple: 12\none-thousand: 1,000\nnegative one-thousand: -1,000")).toEqual(YAML.parse(YAML.dump({
+      'zero': 0,
+      'simple': 12,
+      'one-thousand': 1000,
+      'negative one-thousand': -1000
+    })));
+  });
+  it('can be integers as map keys', function() {
+    return expect(YAML.parse("1: one\n2: two\n3: three")).toEqual(YAML.parse(YAML.dump({
+      1: 'one',
+      2: 'two',
+      3: 'three'
+    })));
+  });
+  it('can be floats', function() {
+    return expect(YAML.parse("a simple float: 2.00\nlarger float: 1,000.09\nscientific notation: 1.00009e+3")).toEqual(YAML.parse(YAML.dump({
+      'a simple float': 2.0,
+      'larger float': 1000.09,
+      'scientific notation': 1000.09
+    })));
+  });
+  it('can be time', function() {
+    var iso8601Date, spaceSeparatedDate, withDatesToTime;
+    iso8601Date = new Date(Date.UTC(2001, 12 - 1, 14, 21, 59, 43, 10));
+    iso8601Date.setTime(iso8601Date.getTime() - 5 * 3600 * 1000);
+    spaceSeparatedDate = new Date(Date.UTC(2001, 12 - 1, 14, 21, 59, 43, 10));
+    spaceSeparatedDate.setTime(spaceSeparatedDate.getTime() - 5 * 3600 * 1000);
+    withDatesToTime = function(input) {
+      var key, res, val;
+      res = {};
+      for (key in input) {
+        val = input[key];
+        res[key] = Math.round(val.getTime() / 1000) * 1000;
+      }
+      return res;
+    };
+    return expect(withDatesToTime(YAML.parse("iso8601: 2001-12-14t21:59:43.10-05:00\nspace seperated: 2001-12-14 21:59:43.10 -05:00"))).toEqual(YAML.parse(YAML.dump(withDatesToTime({
+      'iso8601': iso8601Date,
+      'space seperated': spaceSeparatedDate
+    }))));
+  });
+  return it('can be date', function() {
+    var aDate, withDatesToTime;
+    aDate = new Date(Date.UTC(1976, 7 - 1, 31, 0, 0, 0, 0));
+    withDatesToTime = function(input) {
+      var key, res, val;
+      return input;
+      res = {};
+      for (key in input) {
+        val = input[key];
+        res[key] = Math.round(val.getTime() / 1000) * 1000;
+      }
+      return res;
+    };
+    return expect(withDatesToTime(YAML.parse("date: 1976-07-31"))).toEqual(YAML.parse(YAML.dump(withDatesToTime({
+      'date': aDate
+    }))));
+  });
+});
+
+describe('Dumped YAML Blocks', function() {
+  it('can be single ending newline', function() {
+    return expect(YAML.parse("---\nthis: |\n    Foo\n    Bar")).toEqual(YAML.parse(YAML.dump({
+      'this': "Foo\nBar\n"
+    })));
+  });
+  it('can be single ending newline with \'+\' indicator', function() {
+    return expect(YAML.parse("normal: |\n  extra new lines not kept\n\npreserving: |+\n  extra new lines are kept\n\n\ndummy: value")).toEqual(YAML.parse(YAML.dump({
+      'normal': "extra new lines not kept\n",
+      'preserving': "extra new lines are kept\n\n\n",
+      'dummy': 'value'
+    })));
+  });
+  it('can be multi-line block handling trailing newlines in function of \'+\', \'-\' indicators', function() {
+    return expect(YAML.parse("clipped: |\n    This has one newline.\n\n\n\nsame as \"clipped\" above: \"This has one newline.\\n\"\n\nstripped: |-\n    This has no newline.\n\n\n\nsame as \"stripped\" above: \"This has no newline.\"\n\nkept: |+\n    This has four newlines.\n\n\n\nsame as \"kept\" above: \"This has four newlines.\\n\\n\\n\\n\"")).toEqual(YAML.parse(YAML.dump({
+      'clipped': "This has one newline.\n",
+      'same as "clipped" above': "This has one newline.\n",
+      'stripped': 'This has no newline.',
+      'same as "stripped" above': 'This has no newline.',
+      'kept': "This has four newlines.\n\n\n\n",
+      'same as "kept" above': "This has four newlines.\n\n\n\n"
+    })));
+  });
+  it('can be folded block in a sequence', function() {
+    return expect(YAML.parse("---\n- apple\n- banana\n- >\n    can't you see\n    the beauty of yaml?\n    hmm\n- dog")).toEqual(YAML.parse(YAML.dump(['apple', 'banana', "can't you see the beauty of yaml? hmm\n", 'dog'])));
+  });
+  it('can be folded block as a mapping value', function() {
+    return expect(YAML.parse("---\nquote: >\n    Mark McGwire's\n    year was crippled\n    by a knee injury.\nsource: espn")).toEqual(YAML.parse(YAML.dump({
+      'quote': "Mark McGwire's year was crippled by a knee injury.\n",
+      'source': 'espn'
+    })));
+  });
+  return it('can be folded block handling trailing newlines in function of \'+\', \'-\' indicators', function() {
+    return expect(YAML.parse("clipped: >\n    This has one newline.\n\n\n\nsame as \"clipped\" above: \"This has one newline.\\n\"\n\nstripped: >-\n    This has no newline.\n\n\n\nsame as \"stripped\" above: \"This has no newline.\"\n\nkept: >+\n    This has four newlines.\n\n\n\nsame as \"kept\" above: \"This has four newlines.\\n\\n\\n\\n\"")).toEqual(YAML.parse(YAML.dump({
+      'clipped': "This has one newline.\n",
+      'same as "clipped" above': "This has one newline.\n",
+      'stripped': 'This has no newline.',
+      'same as "stripped" above': 'This has no newline.',
+      'kept': "This has four newlines.\n\n\n\n",
+      'same as "kept" above': "This has four newlines.\n\n\n\n"
+    })));
+  });
+});
+
+describe('Dumped YAML Comments', function() {
+  it('can begin the document', function() {
+    return expect(YAML.parse("# This is a comment\nhello: world")).toEqual(YAML.parse(YAML.dump({
+      hello: 'world'
+    })));
+  });
+  it('can finish a line', function() {
+    return expect(YAML.parse("hello: world # This is a comment")).toEqual(YAML.parse(YAML.dump({
+      hello: 'world'
+    })));
+  });
+  return it('can end the document', function() {
+    return expect(YAML.parse("hello: world\n# This is a comment")).toEqual(YAML.parse(YAML.dump({
+      hello: 'world'
+    })));
+  });
+});
+
+describe('Dumped YAML Aliases and Anchors', function() {
+  it('can be simple alias', function() {
+    return expect(YAML.parse("- &showell Steve\n- Clark\n- Brian\n- Oren\n- *showell")).toEqual(YAML.parse(YAML.dump(['Steve', 'Clark', 'Brian', 'Oren', 'Steve'])));
+  });
+  return it('can be alias of a mapping', function() {
+    return expect(YAML.parse("- &hello\n    Meat: pork\n    Starch: potato\n- banana\n- *hello")).toEqual(YAML.parse(YAML.dump([
+      {
+        Meat: 'pork',
+        Starch: 'potato'
+      }, 'banana', {
+        Meat: 'pork',
+        Starch: 'potato'
+      }
+    ])));
+  });
+});
+
+describe('Dumped YAML Documents', function() {
+  it('can have YAML header', function() {
+    return expect(YAML.parse("--- %YAML:1.0\nfoo: 1\nbar: 2")).toEqual(YAML.parse(YAML.dump({
+      foo: 1,
+      bar: 2
+    })));
+  });
+  it('can have leading document separator', function() {
+    return expect(YAML.parse("---\n- foo: 1\n  bar: 2")).toEqual(YAML.parse(YAML.dump([
+      {
+        foo: 1,
+        bar: 2
+      }
+    ])));
+  });
+  return it('can have multiple document separators in block', function() {
+    return expect(YAML.parse("foo: |\n    ---\n    foo: bar\n    ---\n    yo: baz\nbar: |\n    fooness")).toEqual(YAML.parse(YAML.dump({
+      foo: "---\nfoo: bar\n---\nyo: baz\n",
+      bar: "fooness\n"
+    })));
+  });
+});
+
+url = typeof document !== "undefined" && document !== null ? (ref = document.location) != null ? ref.href : void 0 : void 0;
+
+if (!(url != null) || url.indexOf('file://') === -1) {
+  examplePath = 'spec/example.yml';
+  if (typeof __dirname !== "undefined" && __dirname !== null) {
+    examplePath = __dirname + '/example.yml';
+  }
+  describe('YAML loading', function() {
+    it('can be done synchronously', function() {
+      return expect(YAML.load(examplePath)).toEqual({
+        "this": 'is',
+        a: ['YAML', 'example']
+      });
+    });
+    return it('can be done asynchronously', function(done) {
+      return YAML.load(examplePath, function(result) {
+        expect(result).toEqual({
+          "this": 'is',
+          a: ['YAML', 'example']
+        });
+        return done();
+      });
+    });
+  });
+}
diff --git a/src/main/webapp/scripts/lib/yamljs/test/spec/example.yml b/src/main/webapp/scripts/lib/yamljs/test/spec/example.yml
new file mode 100755
index 0000000000000000000000000000000000000000..17d83eee5cb06ccb52fc1bd889bbdd2c9e65b6a8
--- /dev/null
+++ b/src/main/webapp/scripts/lib/yamljs/test/spec/example.yml
@@ -0,0 +1,4 @@
+this: is
+a:
+    - YAML
+    - example
\ No newline at end of file
diff --git a/src/main/webapp/scripts/utils.js b/src/main/webapp/scripts/utils.js
old mode 100644
new mode 100755
index 2b7a89d9c8abb26d68ced3b7a9a6268578c9352a..f3791d4bbc6db00fba19eb56ba661b7201d57b57
--- a/src/main/webapp/scripts/utils.js
+++ b/src/main/webapp/scripts/utils.js
@@ -622,3 +622,33 @@ function compareByText( obj1, obj2 ){
     return convertToText(obj1) === convertToText(obj2);
 }
 
+// simplest accordion of all
+$.fn.superSimpleAccordion = function(){
+    
+    var container = $(this).show();
+    var h3s = container.find('h3');
+    var divs = h3s.next('div');
+
+    divs.addClass('content').hide();
+    container.find('.active').show();
+    h3s.on('click', function(){
+        var content = $(this).next('div');
+        if (content.is(':hidden')) {
+            h3s.removeClass('active');
+            $(this).addClass('active');
+            divs.removeClass('active').slideUp();
+            content.addClass('active').slideDown();
+        }
+    });
+
+    // add styling to <head> element
+    $('head').append('<style type="text/css" id="accordion-styles">' +
+        '#accordion { font-family: Arial, Helvetica, sans-serif; font-size: 13px; line-height: 17px; }' +
+        '#accordion .active { display: block; }' +
+        '#accordion h3 { font-size: 13px; font-weight: bold; color: #222; padding: 5px 10px; border: 1px solid #d0d0d0; }' +
+        '#accordion h3:hover { cursor: pointer; }' +
+        '#accordion h3.active { background: #04519b; color: #fff; } ' +
+        '#accordion .content { padding: 1em; border: 1px solid #d0d0d0; }' +
+        '</style>');
+    
+};
diff --git a/src/main/webapp/scripts/xnat/ui/table.js b/src/main/webapp/scripts/xnat/ui/table.js
old mode 100644
new mode 100755
index 5f55ba2a5df1862327cbb918857b3863235d1d95..cbe02cdccb15736ae9ccd937b9ade6acc7310b31
--- a/src/main/webapp/scripts/xnat/ui/table.js
+++ b/src/main/webapp/scripts/xnat/ui/table.js
@@ -2,9 +2,27 @@
  * Methods for creating XNAT-specific <table> elements
  */
 
-var XNAT = getObject(XNAT||{});
+var XNAT = getObject(XNAT);
 
-(function(XNAT, $){
+(function(factory){
+
+    // add dependencies to 'imports' array
+    var imports = [
+        'xnat/init',
+        'lib/jquery/jquery'
+    ];
+
+    if (typeof define === 'function' && define.amd) {
+        define(imports, factory);
+    }
+    else if (typeof exports === 'object') {
+        module.exports = factory(XNAT, jQuery);
+    }
+    else {
+        return factory(XNAT, jQuery);
+    }
+    
+}(function(XNAT, $){
 
     var table,
         element = spawn.element,
@@ -18,8 +36,8 @@ var XNAT = getObject(XNAT||{});
      */
     function Table(opts, config){
 
-        this.opts = opts||{};
-        this.config = config||null;
+        this.opts = opts || {};
+        this.config = config || null;
 
         this.table = element('table', this.opts);
         this.table$ = $(this.table);
@@ -31,7 +49,7 @@ var XNAT = getObject(XNAT||{});
 
         // get 'last' item wrapped in jQuery
         this.last$ = function(el){
-            return $(this.last[el||'parent']);
+            return $(this.last[el || 'parent']);
         };
 
         this.setLast = function(el){
@@ -44,7 +62,7 @@ var XNAT = getObject(XNAT||{});
         this.cols = this.columns = [];
 
         // try to init?
-        if (config && config.data){
+        if (config && config.data) {
             this.init(config.data);
         }
     }
@@ -72,14 +90,14 @@ var XNAT = getObject(XNAT||{});
 
     // create a single <td> element
     Table.p.td = function(content, opts){
-        var td = element('td', opts||content, content);
+        var td = element('td', opts || content, content);
         this.last.tr.appendChild(td);
         //this.setLast(td);
         return this;
     };
 
     Table.p.th = function(content, opts){
-        var th = element('th', opts||content, content);
+        var th = element('th', opts || content, content);
         this.last.tr.appendChild(th);
         //this.setLast(th);
         return this;
@@ -88,13 +106,13 @@ var XNAT = getObject(XNAT||{});
     Table.p.tr = function(data, opts){
         var tr = element('tr', opts);
         //data = data || this.data || null;
-        if (data){
+        if (data) {
             [].concat(data).forEach(function(item){
                 tr.appendChild(element('td', item))
             });
         }
         // only add <tr> elements to <table>, <thead>, <tbody>, and <tfoot>
-        if (/(table|thead|tbody|tfoot)/.test(this.last.parent.tagName.toLowerCase())){
+        if (/(table|thead|tbody|tfoot)/.test(this.last.parent.tagName.toLowerCase())) {
             this.last.parent.appendChild(tr);
         }
         this.last.tr = tr;
@@ -110,7 +128,7 @@ var XNAT = getObject(XNAT||{});
         [].concat(data).forEach(function(item){
             tr.appendChild(element('td', item));
         });
-        (this.last.tbody||this.table).appendChild(tr);
+        (this.last.tbody || this.table).appendChild(tr);
         return this;
     };
 
@@ -119,7 +137,7 @@ var XNAT = getObject(XNAT||{});
         var last_tr = this.last.tr;
         [].concat(items).forEach(function(item){
             var td;
-            if (isPlainObject(item)){
+            if (isPlainObject(item)) {
                 td = element('td', '', extend(true, item, opts));
             }
             else {
@@ -134,8 +152,8 @@ var XNAT = getObject(XNAT||{});
 
     Table.p.rows = function(data, opts){
         var _this = this,
-            rows = [];
-        data = data||[];
+            rows  = [];
+        data = data || [];
         data.forEach(function(row){
             rows.push(_this.tr(opts, row))
         });
@@ -160,13 +178,13 @@ var XNAT = getObject(XNAT||{});
 
     // reset last.parent to <tbody>
     Table.p.toBody = Table.p.closestBody = function(){
-        this.setLast(this.last.tbody||this.table);
+        this.setLast(this.last.tbody || this.table);
         return this;
     };
 
     // reset last.parent to <thead>
     Table.p.toHead = Table.p.closestBody = function(){
-        this.setLast(this.last.thead||this.table);
+        this.setLast(this.last.thead || this.table);
         return this;
     };
 
@@ -213,26 +231,28 @@ var XNAT = getObject(XNAT||{});
     Table.p.init = function(data){
 
         var _this = this,
-            obj = {},
+            obj   = {},
             header,
-            cols = 0;
+            cols  = 0;
 
         // don't init twice
-        if (this.inited) { return this }
+        if (this.inited) {
+            return this
+        }
 
         data = data || [];
 
-        if (Array.isArray(data)){
+        if (Array.isArray(data)) {
             obj.data = data;
         }
         else {
-            obj = data||{};
+            obj = data || {};
         }
-        if (obj.header){
+        if (obj.header) {
             // if there's a 'header' property
             // set to true, pick the header from
             // the first row of data
-            if (obj.header === true){
+            if (obj.header === true) {
                 header = obj.data.shift();
             }
             // otherwise it's set explicitly
@@ -245,10 +265,10 @@ var XNAT = getObject(XNAT||{});
 
         // set the number of columns based on
         // the header or first row of data
-        cols = (header) ? header.length : (obj.data[0]||[]).length;
+        cols = (header) ? header.length : (obj.data[0] || []).length;
 
         // add the header
-        if (header){
+        if (header) {
             this.thead();
             this.tr();
             [].concat(header).forEach(function(item){
@@ -259,12 +279,12 @@ var XNAT = getObject(XNAT||{});
         // always add <tbody> element on .init()
         this.tbody();
 
-        [].concat(obj.data||[]).forEach(function(col){
+        [].concat(obj.data || []).forEach(function(col){
             var i = -1;
             // make a row!
             _this.tr();
             // don't exceed column width of header or first column
-            while (++i < cols){
+            while (++i < cols) {
                 _this.td(col[i]);
             }
         });
@@ -274,9 +294,9 @@ var XNAT = getObject(XNAT||{});
         return this;
 
     };
-    
+
     Table.p.render = function(element){
-        if (element){
+        if (element) {
             $$(element).empty().append(this.table);
         }
         return this.table;
@@ -287,34 +307,34 @@ var XNAT = getObject(XNAT||{});
     };
 
     table = function(data, opts){
-        if (!opts){
+        if (!opts) {
             opts = data;
             data = [];
         }
         return new Table(data, opts);
     };
-    
+
     // helper for future XNAT DataTable widget
     table.dataTable = function(data, opts){
-            
+
     };
 
     // table with <input> elements in the cells
     table.inputTable = function(data, opts){
         data = data.map(function(row){
             return row.map(function(cell){
-                if (/string|number/.test(typeof cell)){
-                    return cell+''
+                if (/string|number/.test(typeof cell)) {
+                    return cell + ''
                 }
-                if (Array.isArray(cell)){
+                if (Array.isArray(cell)) {
                     return element('input', extend(true, {}, cell[2], {
-                        name: cell[0],
+                        name:  cell[0],
                         value: cell[1],
-                        data: { value: cell[1] }
+                        data:  { value: cell[1] }
                     }));
                 }
                 cell = extend(true, cell, {
-                    data: { value: cell.value }
+                    data: {value: cell.value}
                 });
                 return element('input', cell);
             });
@@ -325,8 +345,9 @@ var XNAT = getObject(XNAT||{});
         return table.init(data);
     };
 
-    XNAT.ui = getObject(XNAT.ui);
+    XNAT.ui = getObject(XNAT.ui||{});
     XNAT.ui.table = XNAT.table = table;
     XNAT.ui.inputTable = XNAT.inputTable = table.inputTable;
 
-})(XNAT, jQuery);
+}));
+
diff --git a/src/main/webapp/scripts/xnat/ui/tabs.js b/src/main/webapp/scripts/xnat/ui/tabs.js
old mode 100644
new mode 100755
index 1cef6d4fb219644ff3a3762b84a3fa84327fab3b..f5c365b68c65f81371a4e0c47002520190079a79
--- a/src/main/webapp/scripts/xnat/ui/tabs.js
+++ b/src/main/webapp/scripts/xnat/ui/tabs.js
@@ -157,16 +157,14 @@ var XNAT = getObject(XNAT||{});
 
 
         // create <ul> elements for tab groups
-        function setupGroups(list){
+        function setupGroups(groups){
 
             var flippers = __.tabs.flippers.container;
 
             __.tabs.groups = {};
 
-            list.forEach(function(item){
+            $.each(groups, function(name, label){
 
-                var name = item.name;
-                var label = item.label;
                 var id = toDashed(name) + '-tabs';
 
                 var container = spawn('ul.nav.tab-group', {
@@ -180,7 +178,8 @@ var XNAT = getObject(XNAT||{});
                     name: name,
                     label: label,
                     id: id,
-                    container: container
+                    container: container,
+                    children: []
                 };
 
             });
diff --git a/src/main/webapp/scripts/xnat/xhr.js b/src/main/webapp/scripts/xnat/xhr.js
old mode 100644
new mode 100755
index ebf00570d135f390fc95799f7a5a04aa9fedc9d7..d67887263313b7c6c3c36985ea2c316449301336
--- a/src/main/webapp/scripts/xnat/xhr.js
+++ b/src/main/webapp/scripts/xnat/xhr.js
@@ -68,7 +68,8 @@ var XNAT = getObject(XNAT||{}),
     //////////////////////////////////////////////////
 
     // add XNAT.url methods to XNAT.xhr
-    extend(xhr, url);
+    // No, don't do that.
+    //extend(xhr, url);
 
     xhr.$ = getObject(xhr.$||{});
 
@@ -363,13 +364,45 @@ var XNAT = getObject(XNAT||{}),
     // get the remote HTML
     xhr.loadHTML = function( $container, url, data, callback ){
 
-        // need $container and url at the very least
+        var obj = {
+                method: 'GET',
+                dataType: 'html'
+            },
+            success = null;
+        
+        // need $container and url arguments at the very least
         if (arguments.length < 2){
             return new Error('XNAT.xhr.loadHTML() requires the $container and url arguments.');
         }
+        
+        if (isPlainObject(url)){
+            obj = extend(true, {}, url, obj);    
+        }
+        else {
+            obj.url = url;
+            obj.data = data;
+        }
+        
+        // if there's a 'success' property, call it
+        // after appending the HTML
+        if (isFunction(obj.success)){
+            success = obj.success;        
+        }
+        
+        obj.success = function(html){
+            $$($container).empty().append(html);
+            if (success){
+                success.apply(this, arguments);
+            }
+            if (isFunction(callback)){
+                callback.apply(this, arguments);
+            }
+        };
+        
+        return xhr.request(obj);
 
         // make sure we've got a jQuery object and pass to jQuery's .load() method
-        $$($container).load(url, data, callback);
+        //$$($container).load(url, data, callback);
 
     };