diff --git a/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml b/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml
index f6e1311c8fde5ce996662230e772a31353b1c24e..a36dea2ddd51796e2f1cfcfca33354f2dd334e0e 100644
--- a/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml
+++ b/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml
@@ -1,117 +1,908 @@
-# Initial element declarations for the site admin user interface elements.
+tabGroups:
+#    dashboard: Dashboard
+    xnatSetup: XNAT Setup
+    manageAccess: Manage Access
+    manageData: Manage Data
+    processing: Processing
+    projectCustomization: Project Customization
+    advanced: Advanced XNAT Settings
+    other: Other
+
+
 siteId:
+    kind: panel.input.text
+    id: siteId
+    name: siteId
     label: Site ID
-    type: text
-    id: site-id
-    description: Identifies your XNAT site.
-    placeholder: Enter your XNAT site ID...
-    url: /xapi/services/prefs/siteId/{siteId}
-    default: XNAT
-    validation:
-        required: true
-        type: xnat-id
-
+    validation: required id onblur
+    description: "The id used to refer to this site (also used to generate database ids). The Site ID must start with a letter and contain only letters, numbers and underscores. It should be a short, one-word name or acronym which describes your site. No spaces or non-alphanumeric characters."
+siteDescriptionOption:
+    kind: panel.display  # make this a radio button group
+    id: siteDescriptionOption
+    name: siteDescriptionOption
+    label: Site Description
+    value: Select a site description option below
+    # onchange: # some javascript that toggles display of the elements below appropriately
+siteDescriptionOptionText:
+    kind: panel.input.checkbox  # make this a radio button group
+    id: siteDescriptionOptionText
+    label: "Text (Markdown)"
+siteDescriptionOptionPage:
+    kind: panel.input.checkbox  # make this a radio button group
+    id: siteDescriptionOptionPage
+    label: "Page"
+siteDescriptionFile:
+    kind: panel.input.text
+    id: siteDescriptionFile
+    name: siteDescriptionFile
+    label: " "
+    description: "Specify a velocity template file to display on the login page"
+siteDescriptionText:
+    kind: panel.textarea
+    id: siteDescriptionText
+    name: siteDescriptionText
+    label: " "
+    description: "Specify a simple text description of this site"
 siteUrl:
+    kind: panel.input.text
+    id: siteUrl
+    name: siteUrl
     label: Site Url
-    type: url
-    description: The root URL for the site. This is passed to external services.
-    placeholder: Enter the URL for your XNAT site...
-    value: https://cnda.wustl.edu
-    validation:
-        required: true
-
-siteDescriptionPage:
-    id: desc-page
-    label: Page
-    type: site.path
-    description: The page to display for the site description, e.g. /screens/site_description.vm.
-    value: /screens/site_description.vm
-    placeholder: Enter a page for your site description...
-    validation:
-        required: true
-
-siteDescriptionMarkdown:
-    id: desc-markdown
-    label: Text (Markdown)
-    type: markdown
-    tooltip: XNAT allows you to use GitHub-flavored Markdown to create and format your own site description. [>> View Tutorial](http://foobar)
-    description: Compose a site description without referencing a template or site page.
-    validation:
-        required: true
-
-siteDescription:
-    label: Site Description
-    type: composite
-    selection: radio
-    url: /data/services/prefs/{desc}
-    children:
-        ${siteDescriptionPage}
-        ${siteDescriptionMarkdown}
-
-landingPage:
-    label: Landing Page
-    type: site.path
-    description: The page to display when the user logs in.
-    value: /screens/QuickSearch.vm
-    placeholder: Enter the default landing page...
-    overrides:
-        target: homePage
-        type: checkbox
-        position: right
-        description: Use this as my home page.
-        hideTarget: false
-    validation:
-        required: true
-
-homePage:
-    label: Home Page
-    type: site.path
-    description: The page to display when the user clicks the home link.
-    value: /screens/AdminUsers.vm
-    placeholder: Enter the default home page...
-    validation:
-        required: true
+    validation: required id
+    description: ""
+adminEmail:
+    kind: panel.input.email
+    id: adminEmail
+    name: adminEmail
+    label: Site Administrator Email Address
+    url: /xapi/siteConfig/adminEmail
+    value: ''
 
+fileSystemSettingsWarning:
+    someInfo:
+        tag: div.message
+        element:
+            html: "These settings must be defined during initial application configuration, and are seldom - if ever - changed."
+            style:
+                fontWeight: bold
+archiveRootPath:
+    kind: panel.input.text
+    id: archiveRootPath
+    name: archiveRootPath
+    label: Archive Root Path
+    validation: required path
+    description: ""
+    value: ??XNAT.data.siteConfig.archiveRootPath
+cachePath:
+    kind: panel.input.text
+    id: cachePath
+    name: cachePath
+    label: Cache Path
+    validation: required path
+    description: ""
+    value: ??XNAT.data.siteConfig.cachePath
+prearchivePath:
+    kind: panel.input.text
+    id: prearchivePath
+    name: prearchivePath
+    label: Prearchive Path
+    validation: required path
+    description: ""
+    value: ??XNAT.data.siteConfig.prearchivePath
+ftpPath:
+    kind: panel.input.text
+    id: ftpPath
+    name: ftpPath
+    label: FTP Path
+    validation: required path
+    description: ""
+    value: ??XNAT.data.siteConfig.cachePath
+buildPath:
+    kind: panel.input.text
+    id: buildPath
+    name: buildPath
+    label: Build Path
+    validation: required id onblur
+    description: ""
+    value: ??XNAT.data.siteConfig.cachePath
+pipelinePath:
+    kind: panel.input.text
+    id: pipelinePath
+    name: pipelinePath
+    label: Pipeline Path
+    validation: required id onblur
+    description: ""
+    value: ??XNAT.data.siteConfig.cachePath
+dataFolders:
+    kind: panel.input.text
+    id: dataFolders
+    name: dataFolders
+    label: Data Folders
+    validation: required id onblur
+    description: ""
+    
 siteInfo:
+    kind: panel.form
+    name: siteInfo
     label: Site Information
-    type: panel
-    controls:
-       ${siteId}
-       ${siteUrl}
-       ${siteDescription}
-       ${landingPage}
-       ${homePage}
-
-siteAdminEmail:
-    label: Site Admin Email
-    type: email
-    placeholder: Administrator email address...
-    url: /xapi/services/prefs/siteAdminEmail/{siteAdminEmail}
-    validation:
-        required: true
+    method: POST
+    action: /xapi/siteConfig/batch
+    load:
+        lookup: XNAT.data.siteConfig
+    contentType: json
+    contents:
+        ${siteId}
+        ${siteUrl}
+        ${siteDescriptionOption}
+        ${siteDescriptionOptionText}
+        ${siteDescriptionOptionPage}
+        ${siteDescriptionFile}
+        ${siteDescriptionText}
 
 adminInfo:
+    kind: panel.form
+    name: adminInfo
     label: Admin Information
-    type: panel
-    controls:
-        ${siteAdminEmail}
+    contents:
+        ${adminEmail}
+
+generalSecuritySettings:
+    kind: panel.form
+    name: generalSecuritySettings
+    label: General Site Security Settings
+    contents:
+        securityChannel:
+            kind: panel.select.single
+            id: securityChannel
+            name: security.channel
+            label: Security Channel
+            value: https
+            options:
+                # value: label # <- simplest setup
+                http: http
+                https: https
+            element:
+                id: security-channel
+                title: Security Channel
+        requireUserLogin:
+            kind: panel.input.checkbox
+            id: requireUserLogin
+            name: require_login
+            label: Require User Login
+            description: "If checked, then only registered users will be able to access your site. If false, anyone visiting your site will automatically be logged in as 'guest' with access to public data."
+
+userLoginsSessionControls:
+    kind: panel.form
+    name: userLoginsSessionControls
+    label: User Logins / Session Controls
+    method: POST
+    action: /xapi/siteConfig/batch
+    load:
+        lookup: XNAT.data.siteConfig
+        refresh: /xapi/siteConfig
+    contents:
+        sessionTimeout:
+            kind: panel.input.number
+            id: sessionTimeout
+            name: security.token_timeout
+            label: Session Timeout
+            value: 15
+            description: "Interval for timing out alias tokens. Uses PostgreSQL interval notation: http://www.postgresql.org/docs/9.0/static/functions-datetime.html"
+        sessionTimeoutMessage:
+            kind: panel.input.text
+            id: sessionTimeoutMessage
+            name: sessionTimeoutMessage
+            label: Session Timeout Message
+            value: "Your session has timed out."
+            description: Alert message provided to users after a session timeout and logout.
+        allowResumeOnNextLogin:
+            kind: panel.input.checkbox
+            id: allowResumeOnNextLogin
+            name: allowResumeOnNextLogin
+            label: Allow Resume On Next Login?
+            value: true
+            description: Allow user to resume where they left off, if logging back in after a session timeout?
+        maximumConcurrentSessions:
+            kind: panel.input.number
+            id: maximumConcurrentSessions
+            name: security.sessions.concurrent_max
+            label: Maximum Concurrent Sessions
+            value: 5
+            description: The maximum number of permitted sessions a user can have open simultaneously
+        loginFailureMessage:
+            kind: panel.input.text
+            id: loginFailureMessage
+            name: UI.login_failure_message
+            label: Login Failure Message
+            value: Login failed
+            description: Text to show when a user fails to login
+        maximumFailedLogins:
+            kind: panel.input.number
+            id: maximumFailedLogins
+            name: security.max_failed_logins
+            label: Maximum Failed Logins
+            value: -1
+            description: Number of failed login attempts before accounts are temporarily locked. (-1 disables feature)
+        failedLoginLockoutDuration:
+            kind: panel.input.number
+            id: failedLoginLockoutDuration
+            name: security.max_failed_logins_lockout_duration
+            label: Failed Login Lockout Duration
+            value: 0
+            description: Number of milliseconds to lock user accounts that have exceeded the max_failed_logins count. Select (3600000 for 1 hour, 86400000 for 24 hours)
+        userInactivityLockout:
+            kind: panel.input.number
+            id: userInactivityLockout
+            name: security.inactivity_before_lockout
+            label: User Inactivity Lockout
+            value: 31556926
+            description: Number of seconds of inactivity before an account is disabled (31556926 for 1 year)
+
+passwords:
+    kind: panel.form
+    name: passwords
+    label: Passwords
+    method: POST
+    action: /xapi/siteConfig/batch
+    load:
+        lookup: XNAT.data.siteConfig
+    contentType: json
+    contents:
+        passwordComplexity:
+            kind: panel.input.text
+            id: passwordComplexity
+            name: passwordComplexity
+            label: Password Complexity
+            value: "^.*$"
+            description: Must be a valid regular expression.
+        passwordComplexityMessage:
+            kind: panel.input.text
+            id: passwordComplexityMessage
+            name: passwordComplexityMessage
+            label: Password Complexity Message
+            value: "Your password must contain..."
+        passwordHistoryDuration:
+            kind: panel.input.number
+            id: passwordHistoryDuration
+            name: passwordHistoryDuration
+            label: Password History (Duration)
+            value: -1
+            description: "-1 to disable"
+        passwordExpirationInterval:
+            kind: panel.input.number
+            id: passwordExpirationInterval
+            name: passwordExpirationInterval
+            label: Password Expiration (Interval)
+            value: -1
+            description: "-1 to disable"
+        passwordExpirationDate:
+            kind: panel.input.text
+            id: passwordExpirationDate
+            name: passwordExpirationDate
+            label: Password Expiration (Date)
+            description: Dates must be formatted MM/DD/YYYY
+        passwordReuseRestriction:
+            kind: panel.select.single
+            id: passwordReuseRestriction
+            name: passwordReuseRestriction
+            label: Password Reuse Restriction
+        requireSaltedPasswords:
+            kind: panel.input.checkbox
+            id: requireSaltedPasswords
+            name: requireSaltedPasswords
+            label: Require Passwords To Be Salted
+            value: false
+
+csrf:
+    kind: panel.form
+    name: csrf
+    label: CSRF
+    contents:
+        enableCsrfToken:
+            kind: panel.input.checkbox
+            id: enableCsrfToken
+            name: enableCsrfToken
+            label: Require CSRF Token?
+            value: true
+            description: Should this site require the use of a token to prevent CSRF attacks on POST, PUT, and DELETEs?
+        csrfEmailAlert:
+            kind: panel.input.checkbox
+            id: csrfEmailAlert
+            name: enable_csrf_email_alert
+            label: CSRF Email Alert
+            value: "Should this site send an email to the site admin whenever a CSRF attack is attempted?"
+
+auditTrail:
+    kind: panel.form
+    name: auditTrail
+    label: Audit Trail Configuration
+    contents:
+        enableAuditTrail:
+            kind: panel.input.checkbox
+            id: enableAuditTrail
+            name: audit.enable
+            label: Enable Audit Trail?
+            value: true
+        allowUserJustificationForChanges:
+            kind: panel.input.checkbox
+            id: allowUserJustificationForChanges
+            name: audit.show_change_justification
+            label: Allow User Justification For Changes
+            value: true
+            description: "Allow users to enter change justifications when modifying data"
+        requireUserJustificationForChanges:
+            kind: panel.input.checkbox
+            id: requireUserJustificationForChanges
+            name: audit.require_change_justification
+            label: Require User Justification For Changes
+            value: false
+            description: "Force users to enter change justifications when modifying data"
+
+            
+emailServerSettings:
+    kind: panel.form
+    method: POST
+    action: /xapi/notifications/all
+    load: # load data
+        url: /xapi/siteConfig
+#        method: GET      # defaults to GET if not set
+        prop: smtpServer # which root property? gets the root object if not set
+#        lookup: XNAT.data.siteConfig.smtpServer
+    contentType: json
+    name: emailServerSettings
+    label: Mail Server Settings
+    contents:
+        hostname:
+            kind: panel.input.text
+            name: host
+            label: Host
+            value: localhost
+        port:
+            kind: panel.input.number
+            name: port
+            label: Port
+            value: ""
+            element:
+                placeholder: 587
+        username:
+            kind: panel.input.text
+            name: username
+            label: Username
+            url: /xapi/notifications/username
+            value: ''
+            element:
+                placeholder: name@host.org
+        password:
+            kind: panel.input.password
+            name: password
+            label: Password
+            value: ''
+        protocol:
+            kind: panel.input.text
+            name: protocol
+            label: Protocol
+            value: ''
+            #data:
+                #get: GET|/xapi/siteConfig|smtpServer.protocol
+                #set: POST|/xapi/notifications/protocol
+        # subhead breaks up panel items
+        mailServerProperties:
+            kind: panel.subhead
+            name: mailServerProperties
+            label: Properties
+        smtpAuth:
+            kind: panel.input.checkbox
+            name: mail.smtp.auth
+            label: SMTP Auth?
+            #value: true
+            checked: true
+        smtpStartTls:
+            kind: panel.input.checkbox
+            name: mail.smtp.starttls.enable
+            label: Smart TLS?
+            #value: true
+            checked: true
+        smtpSSLTrust:
+            kind: panel.input.text
+            name: mail.smtp.ssl.trust
+            label: SSL Trust
+            value: ''
+            element:
+                placeholder: localhost
 
-siteSetup:
-    label: Site Setup
-    type: tab
+notifications:
+    kind: panel.form
+    name: notifications
+    label: Notifications
     contents:
-        ${siteInfo}
-        ${adminInfo}
+        helpContactInfo:
+            kind: panel.input.email
+            id: helpContactInfo
+            name: helpContactInfo
+            label: "Help Contact Info"
+        emailMessageUserRegistration:
+            kind: panel.input.text                     # textarea
+            id: emailMessageUserRegistration
+            name: emailMessageUserRegistration
+            label: "Email Message: User Registration"
+            description: "Text of message emailed to users upon registration. Link for email validation is auto-populated."
+        emailMessageForgotUsernameRequest:
+            kind: panel.input.text                     # textarea
+            id: emailMessageForgotUsernameRequest
+            name: emailMessageForgotUsernameRequest
+            label: "Email Message: Forgot Username Request"
+            description: "Text of message emailed to users upon lost username request."
+        emailMessageForgotPasswordReset:
+            kind: panel.input.text                     # textarea
+            id: emailMessageForgotPasswordReset
+            name: emailMessageForgotPasswordReset
+            label: "Email Message: Password Reset"
+            description: "Text of message emailed to users upon lost password reset. Link for password reset is auto-populated"
+        notifyAdminUserRegistration:
+            kind: panel.input.checkbox
+            id: notifyAdminUserRegistration
+            name: "email:new_user_registration"
+            label: "Notify Admin on User Registration"
+            value: true
+            description: "Whether to cc admin user on new user emails. Requires valid admin email address."
+        notifyAdminPipelineEmails:
+            kind: panel.input.checkbox
+            id: notifyAdminPipelineEmails
+            name: "email:pipeline"
+            label: "Notify Admin on Pipeline Emails"
+            value: false
+            description: "Whether to cc admin user on pipeline processing submit. Requires valid admin email address."
+        notifyAdminProjectAccessRequest:
+            kind: panel.input.checkbox
+            id: notifyAdminProjectAccessRequest
+            name: "email:project_access"
+            label: "Notify Admin on Project Access Request"
+            value: false
+            description: "Whether to cc admin user on user project access request. Requires valid admin email address."
+        notifyAdminSessionTransfer:
+            kind: panel.input.checkbox
+            id: notifyAdminSessionTransfer
+            name: "email:transfer"
+            label: "Notify Admin on Session Transfer"
+            value: false
+            description: "Whether to cc admin user on session transfer by user. Requires valid admin email address."
+        emailRecipientErrorMessages:
+            kind: panel.input.email
+            id: emailRecipientErrorMessages
+            name: "Notifications:Error"
+            label: "Email Recipient: Error Messages"
+            description: "What email address should receive error emails"
+        emailRecipientIssueReports:
+            kind: panel.input.email
+            id: emailRecipientIssueReports
+            name: "Notifications:Issues"
+            label: "Email Recipient: Issue Reports"
+            description: "What email address should receive issue reports"
+        emailRecipientNewUserAlert:
+            kind: panel.input.email
+            id: emailRecipientNewUserAlert
+            name: "Notifications:NewUser"
+            label: "Email Recipient: New User Alert"
+            description: "What email address should receive New User Registration emails"
+        emailRecipientUpdate:
+            kind: panel.input.email
+            id: emailRecipientUpdate
+            name: "Notifications:Update"
+            label: "Email Recipient: Updates"
+            description: "What email address should receive update emails"
+        notifyAdminSessionTransfer:
+            kind: panel.input.checkbox
+            id: notifyAdminSessionTransfer
+            name: "email:transfer"
+            label: "Notify Admin on Session Transfer"
+            value: false
+            description: "Whether to cc admin user on session transfer by user. Requires valid admin email address."
 
-xnatSetup:
-    label: XNAT Setup
-    type: tabGroup
-    tabs:
-        ${siteSetup}
+themeManagement:
+    kind: panel
+    name: themeManagement
+    label: Theme Management
+    contents:
+        themeScript:
+            tag: script|type=text/javascript;src=../../scripts/themeManagement.js
+        themeStyle:
+            tag: style
+            element:
+                html: ".themeUploader{width:270px;display:inline !important;}"
+        currentTheme:
+            kind: panel.display
+            id: currentTheme
+            label: Current Theme
+            description: The currently selected global theme.
+            value: None
+            element:
+                style:
+                    color: 'red'
+        existingTheme:
+            kind: panel.select.single
+            id: themeSelection
+            label: Select an existing theme
+            description: Selected a new global theme from those available on the system.
+            value: None
+            options:
+                default:
+                    label: None
+                    value: None
+            after:
+                - "<span style=\"position: relative; top: -49px;left: 270px;\">&nbsp;<button id=\"submitThemeButton\" onclick=\"setTheme();\">Set Theme</button>&nbsp;&nbsp;<button id=\"removeThemeButton\" onclick=\"removeTheme();\">Remove Theme</button></span>"
+        uploadTheme:
+            kind: panel.input.upload
+            id: themeFileUpload
+            name: xnat.theme.upload
+            label: Upload a theme package
+            description: Upload a zipped theme package for selection above.
+            className: themeUploader
+          
+
+          
+authenticationMethods:
+    kind: panel.form
+    name: authenticationMethods
+    label: User Authentication Methods
+    contents:
+        xnatInternal:
+            kind: panel.input.checkbox
+            id: xnatInternal
+            name: provider.providers.xnatInternal
+            label: XNAT (Internal)
+            value: true
+        ldapProvider:
+            kind: panel.input.checkbox
+            id: ldapProvider
+            name: provider.providers.ldap
+            label: LDAP
+            value: false
+        oauthProvider:
+            kind: panel.input.checkbox
+            id: oauthProvider
+            name: provider.providers.oauth
+            label: OAuth
+            value: false
+genericAuthenticationProvider:
+    kind: panel.form
+    name: genericAuthenticationProvider
+    label: Generic Authentication Provider
+    contents:
+        providerDbName:
+            kind: panel.input.text
+            id: providerDbName
+            name: providerDbName
+            label: "Provider DB Name"
+        providerDbId:
+            kind: panel.input.text
+            id: providerDbId
+            name: providerDbId
+            label: "Provider DB ID"
+        providerDbType:
+            kind: panel.input.text
+            id: providerDbType
+            name: providerDbType
+            label: "Provider DB Type"
+ldapAuthentication:
+    kind: panel.form
+    name: ldapAuthenticationProvider
+    label: LDAP Authentication Provider
+    contents:
+        ldapName:
+            kind: panel.input.text
+            id: ldapName
+            name: ldapName
+            label: "LDAP Name"
+        ldapId:
+            kind: panel.input.text
+            id: ldapId
+            name: ldapId
+            label: "LDAP ID"
+        ldapType:
+            kind: panel.input.text
+            id: ldapType
+            name: ldapType
+            label: "LDAP Type"
+        ldapAddress:
+            kind: panel.input.text
+            id: ldapAddress
+            name: ldapAddress
+            label: "LDAP Address"
+        ldapUserDomain:
+            kind: panel.input.text
+            id: ldapUserDomain
+            name: ldapUserDomain
+            label: "LDAP User Domain"
+        ldapPassword:
+            kind: panel.input.text
+            id: ldapPassword
+            name: ldapPassword
+            label: "LDAP Password"
+        ldapSearchBase:
+            kind: panel.input.text
+            id: ldapSearchBase
+            name: ldapSearchBase
+            label: "LDAP Search Base"
+        ldapSearchFilter:
+            kind: panel.input.text
+            id: ldapSearchFilter
+            name: ldapSearchFilter
+            label: "LDAP Search Filter"
 
+users:
+    kind: panel
+    name: users
+    label: Users
+#    contents:
+
+userRoles:
+    kind: panel
+    name: userRoles
+    label: User Roles
+#    contents:
+
+registrationOptions:
+    kind: panel.form
+    name: registrationOptions
+    label: Registration Options
+    contents:
+        requireEmailVerificationToRegister:
+            kind: panel.input.checkbox
+            id: requireEmailVerificationToRegister
+            name: emailVerification
+            label: "Require Email Verification To Register?"
+            value: true
+        emailVerificationMessage:
+            kind: panel.input.text                        #textarea
+            id: emailVerificationMessage
+            name: emailVerificationMessage
+            label: "Email Verification Message"
+            value: ""
+        emailVerificationExpiration:
+            kind: panel.input.number
+            id: emailVerificationExpiration
+            name: emailVerificationExpiration
+            label: "Email Verification Expiration"
+            value: 60000
+        autoEnableUserRegistration:
+            kind: panel.input.checkbox
+            id: autoEnableUserRegistration
+            name: autoEnableUserRegistration
+            label: "Auto-enable User Registration?"
+            value: false
+        allowUserCommentsOnRegistration:
+            kind: panel.input.checkbox
+            id: allowUserCommentsOnRegistration
+            name: allowUserCommentsOnRegistration
+            label: "Allow User Comments on Registration?"
+            value: true
+        restrictAccessToUserList:
+            kind: panel.input.checkbox
+            id: restrictAccessToUserList
+            name: restrictAccessToUserList
+            label: "Restrict Access to User List?"
+            value: true
+
+dataReporting:
+    kind: panel.form
+    name: dataReporting
+    label: Data Reporting
+    group: manageData
+    contents:
+        enableAdvancedSearch:
+            kind: panel.input.checkbox
+            id: enableAdvancedSearch
+            name: enableAdvancedSearch
+            label: "Enable Advanced Search"
+            value: true
+
+dicomScpReceivers:
+    kind: panel.form
+    name: dicomScpReceivers
+    label: DICOM SCP Receivers
+    contents:
+        enableDicomReceiver:
+            kind: panel.input.checkbox
+            id: enableDicomReceiver
+            name: enableDicomReceiver
+            label: DICOM Receiver Enabled?
+            value: true
+            description: "Should the DICOM receiver listen for connections?"
+        someInfo:
+            tag: div.message
+            element:
+                html: "Caution: Changes to this setting will take effect immediately. Before disabling the receiver, verify that there are no transmissions currently in progress."
+                style:
+                    fontWeight: bold
+        # hidden form element...
+        # enableDicomReceiver.property.changed.listener
+        # org.nrg.dcm.DicomSCPSiteConfigurationListener
+        dicomHost:
+            kind: panel.input.text
+            id: dicomHost
+            name: dicomHost
+            label: DICOM Host
+            description: "Hostname(s) for DICOM Receiver(s)"
+        dicomAeTitle:
+            kind: panel.input.text
+            id: dicomAeTitle
+            name: dicomAeTitle
+            label: DICOM AE Title
+            description: "AE Title(s) for DICOM Receiver(s)"
+        dicomPortNumber:
+            kind: panel.input.number
+            id: dicomPortNumber
+            name: dicomPortNumber
+            label: DICOM Host
+            description: "Port for DICOM Receiver(s)"
+        defaultDicomReceiver:
+            kind: panel.select.single
+            id: defaultDicomReceiver
+            name: services.dicom.scp.aetitle
+            label: Default DICOM Receiver
+            description: "AE Title for default DICOM receiver"
+        defaultDicomReceiverUser:
+            kind: panel.input.text
+            id: defaultDicomReceiverUser
+            name: services.dicom.scp.receivedfileuser
+            label: "Default DICOM Receiver: User"
+            value: Login failed
+            description: "User account for default DICOM receiver"
+            
+fileSystem:
+    kind: panel.form
+    name: fileSystem
+    label: File System
+    method: POST
+    action: /xapi/siteConfig/batch
+    load:
+        lookup: XNAT.data.siteConfig
+    contentType: json
+    contents:
+        ${archiveRootPath}
+        ${cachePath}
+        ${prearchivePath}
+        ${ftpPath}
+        ${buildPath}
+        ${pipelinePath}
+        ${dataFolders}
+
+
+#################################################
+####  Root Site Admin Spawner Config Object  ####
+#################################################
 siteAdmin:
+    kind: tabs
+    name: siteAdmin
     label: Administer XNAT
-    type: page
+    meta:
+        ${tabGroups}
     contains: tabs
+    tabs:  # this property name is the same as 'contains', so it will be treated like 'contents'
+        siteSetup:
+            kind: tab
+            name: siteSetup
+            label: Site Setup
+            group: xnatSetup
+            active: true
+            contains: panels # the value for 'contains' can be a custom name for 'contents'
+            panels:
+                ${siteInfo}
+                ${adminInfo}
+        security:
+            kind: tab
+            name: security
+            label: Security
+            group: xnatSetup
+            contents:
+                ${generalSecuritySettings}
+                ${userLoginsSessionControls}
+                ${passwords}
+                ${csrf}
+                ${auditTrail}
+        emailServer:
+            kind: tab
+            name: emailServer
+            label: Email Server
+            group: xnatSetup
+            contents:
+                ${emailServerSettings}
+        notifications:
+            kind: tab
+            name: notifications
+            label: Notifications
+            group: xnatSetup
+            contents:
+                ${notifications}
+        themesAndFeatures:
+            kind: tab
+            name: themesAndFeatures
+            label: Themes & Features
+            group: xnatSetup
+            contents:
+                ${themeManagement}
+        authenticationMethods:
+            kind: tab
+            name: authenticationMethods
+            label: Authentication Methods
+            group: manageAccess
+            contents:
+                ${authenticationMethods}
+                ${genericAuthenticationProvider}
+                ${ldapAuthentication}
+        users:
+            kind: tab
+            name: users
+            label: Users
+            group: manageAccess
+            contents:
+                ${users}
+        userRoles:
+            kind: tab
+            name: userRoles
+            label: User Roles
+            group: manageAccess
+            contents:
+                ${userRoles}
+        registrationOptions:
+            kind: tab
+            name: registrationOptions
+            label: Registration Options
+            group: manageAccess
+            contents:
+                ${registrationOptions}
+        dataReporting:
+            kind: tab
+            name: dataReporting
+            label: Data Reporting
+            group: manageData
+            contents:
+                ${dataReporting}
+        dicomScpReceivers:
+            kind: tab
+            name: dicomScpReceivers
+            label: DICOM SCP Receivers
+            group: advanced
+            contents:
+                ${dicomScpReceivers}
+        fileSystem:
+            kind: tab
+            name: fileSystem
+            label: File System
+            group: advanced
+            contents:
+                ${fileSystem}
+                ${siteIdMain}
+
+
+###########################################
+####  Initial Site Setup Admin Panels  ####
+###########################################
+siteIdMain:
+    kind: panel.form
+    name: siteIdMain
+    label: Site Identification
+    contents:
+        ${siteId}
+        ${siteUrl}
+        ${adminEmail}
+sitePaths:
+    kind: panel.form
+    name: sitePaths
+    label: Site Paths
+    contents:
+#        ${archiveRootPath}
+#        ${cachePath}
+#        ${prearchivePath}
+#        ${ftpPath}
+#        ${buildPath}
+#        ${pipelinePath}
+        ${dataFolders}
+initialSetup:
+    kind: panel
+    name: initialSetup
+    label: Initial XNAT Setup
     contents:
-        ${xnatSetup}
+#        ${siteIdMain}
+#        ${smtpServer}
+        ${sitePaths}
diff --git a/src/main/webapp/WEB-INF/conf/xnat-security.xml b/src/main/webapp/WEB-INF/conf/xnat-security.xml
index 1e8e4fee240203fb57e1a83945a95ca99c920f99..5ec44a3b884f4364b7b234350173e0e0a530d6c0 100644
--- a/src/main/webapp/WEB-INF/conf/xnat-security.xml
+++ b/src/main/webapp/WEB-INF/conf/xnat-security.xml
@@ -146,6 +146,7 @@
                 <value>/app/template/XDATScreen_manage_pipeline.vm*</value>
                 <value>/app/template/XDATScreen_roles.vm*</value>
                 <value>/monitoring*</value>
+                <value>/setup/**</value>
             </list>
         </property>
     </bean>
@@ -221,7 +222,8 @@
 	
 	<bean id="xnatInitCheckFilter" class="org.nrg.xnat.security.XnatInitCheckFilter">
         <property name="initializationPath" value="/services/settings/initialize"/>
-        <property name="configurationPath" value="/app/template/Configuration.vm"/>
+        <!--<property name="configurationPath" value="/app/template/Configuration.vm"/>-->
+        <property name="configurationPath" value="/setup"/>
         <property name="nonAdminErrorPath" value="/app/template/Unconfigured.vm"/>
         <property name="exemptedPaths">
             <list value-type="java.lang.String">
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..67e051f33a773a6ea556ada94b2abe85d57eb0de
--- a/src/main/webapp/WEB-INF/tags/page/head.tag
+++ b/src/main/webapp/WEB-INF/tags/page/head.tag
@@ -12,36 +12,17 @@
 
     <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}"/>
 
-    <link type="text/css" rel="stylesheet" href="${rootUrl}/scripts/lib/bootstrap/themes/xnat/bootstrap.css">
+    <%--<link type="text/css" rel="stylesheet" href="${rootUrl}/scripts/lib/bootstrap/themes/xnat/bootstrap.css">--%>
     <link type="text/css" rel="stylesheet" href="${rootUrl}/style/app.css">
 
     <%--<script src="<c:url value="/scripts/polyfills.js"/>"></script>--%>
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..607e3b05a9ffde7fa92a2231eccc97b0954028a8
--- a/src/main/webapp/WEB-INF/tags/page/init.tag
+++ b/src/main/webapp/WEB-INF/tags/page/init.tag
@@ -5,7 +5,7 @@
 
 <%-- set empty user info --%>
 <c:set var="loggedIn" value="false" scope="session"/>
-<c:set var="username" value="0" scope="session"/>
+<c:set var="username" value="-" scope="session"/>
 <c:set var="isAdmin" value="false" scope="session"/>
 
 <%-- set vars for user --%>
@@ -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/restricted.tag b/src/main/webapp/WEB-INF/tags/page/restricted.tag
new file mode 100644
index 0000000000000000000000000000000000000000..dac0c39506843ab55776701d2b49337bd20aa8e5
--- /dev/null
+++ b/src/main/webapp/WEB-INF/tags/page/restricted.tag
@@ -0,0 +1,35 @@
+<%@ 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="msg" %>
+
+<%-- restricts access to only admin users --%>
+
+<c:if test="${empty hasInit}">
+    <pg:init>
+        <c:if test="${empty hasVars}">
+            <pg:jsvars/>
+        </c:if>
+    </pg:init>
+</c:if>
+
+<c:choose>
+    <c:when test="${isAdmin == true}">
+
+        <jsp:doBody/>
+
+    </c:when>
+    <c:otherwise>
+
+        <c:choose>
+            <c:when test="${not empty msg}">
+                ${msg}
+            </c:when>
+            <c:otherwise>
+                <p>(not authorized)</p>
+            </c:otherwise>
+        </c:choose>
+
+    </c:otherwise>
+</c:choose>
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 100644
index 0000000000000000000000000000000000000000..16de4bd16c03daad438c390c7f95e919d6e35021
--- /dev/null
+++ b/src/main/webapp/WEB-INF/tags/page/xnat.tag
@@ -0,0 +1,702 @@
+<%@ 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="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>
+    <script src="${_siteRoot}/scripts/lib/jquery-plugins/jquery.dataAttr.js"></script>
+    <script src="${_siteRoot}/scripts/lib/jquery-plugins/jquery.form.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/lib/yamljs/dist/yaml.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"
+            },
+            data: {
+                context: {}
+            }
+        });
+
+    </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}">
+
+    <%-- styles for tabbed interface --%>
+    <%-- TODO: rename and move file or integrate it into app.css --%>
+    <link rel="stylesheet" type="text/css" href="${_siteRoot}/page/admin/style.css?${versionString}">
+
+
+    <!-- legacy XNAT scripts -->
+    <link rel="stylesheet" type="text/css" href="${_siteRoot}/scripts/xmodal-v1/xmodal.css?${versionString}">
+    <script src="${_siteRoot}/scripts/xmodal-v1/xmodal.js"></script>
+    <script src="${_siteRoot}/scripts/xmodal-v1/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/templates.js"></script>
+    <script src="${_siteRoot}/scripts/xnat/ui/input.js"></script>
+    <script src="${_siteRoot}/scripts/xnat/ui/select.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/xnat/spawner.js"></script>
+
+    <%--<script src="${_siteRoot}/scripts/timeLeft.js"></script>--%>
+
+    ${headBottom}
+
+</head>
+<body id="xnat-app" class="xnat app">
+
+${bodyTop}
+
+<div id="user_bar">
+    <div class="inner">
+
+        <c:if test="${_user != '-'}">
+
+            <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">-:--:--</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>--%>
+
+        </c:if>
+
+        <div class="clear"></div>
+    </div>
+</div><!-- /user_bar -->
+
+<c:if test="${_user != '-' && page != 'setup'}">
+
+    <div id="main_nav">
+        <div class="inner">
+
+            <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('${_siteRoot}/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:subjectData">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
+                    });
+
+
+                    window.logged_in = true;
+
+                </script>
+            </form>
+
+            <!-- main_nav interactions -->
+            <script type="text/javascript">
+
+                (function(){
+
+                    // cache it
+                    var main_nav$ = jq('#main_nav ul.nav');
+
+                    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>
+        <!-- /.inner -->
+
+    </div>
+    <!-- /#main_nav -->
+
+</c:if>
+
+<div id="page_wrapper">
+
+    <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'});
+
+        })();
+
+        // 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 1.7"><img
+            src="${_siteRoot}/images/xnat_power_small.png"></a>
+    <small>version 1.7</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-v1/loading_bar.gif" alt="loading">
+</div>
+
+${bodyBottom}
+
+</body>
\ No newline at end of file
diff --git a/src/main/webapp/WEB-INF/tags/spawner/layout.tag b/src/main/webapp/WEB-INF/tags/spawner/layout.tag
index b096d29672bf34e178c3f841b9d897b312a33cfb..46f475fee24d2a5de2dd4c140f71ca2dbbc7d162 100644
--- a/src/main/webapp/WEB-INF/tags/spawner/layout.tag
+++ b/src/main/webapp/WEB-INF/tags/spawner/layout.tag
@@ -292,7 +292,7 @@
                 <script>
                     $('#nav-home').css({
                         width: '30px',
-                        backgroundImage: "url('/images/xnat-nav-logo-white-lg.png')",
+                        backgroundImage: "url('${siteRoot}/images/xnat-nav-logo-white-lg.png')",
                         backgroundRepeat: 'no-repeat',
                         backgroundSize: '32px',
                         backgroundPosition: 'center'
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..c4eaa07b2f548b70ba47bcec4034bca7d0244046
--- a/src/main/webapp/page/admin/content.jsp
+++ b/src/main/webapp/page/admin/content.jsp
@@ -2,163 +2,69 @@
 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
 <%@ taglib prefix="pg" tagdir="/WEB-INF/tags/page" %>
 
-<c:if test="${empty hasInit}">
-    <pg:init/>
-</c:if>
+<pg:restricted>
 
-<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>
-
-        <div id="page-header">
-            <div class="pad">
-                <a href="${siteRoot}/"><img src="${siteRoot}/images/logo.png" style="vertical-align:baseline;"></a>
-                <span style="position:relative;bottom:4px;left:20px;">
-                    XNAT currently contains
-                    <a href="#">16 projects</a>,
-                    <a href="#">47 subjects</a>, and
-                    <a href="#">15 imaging sessions</a>.
-                </span>
-            </div>
-        </div><!-- /#page-header -->
-        <div id="page-body">
-            <div class="pad">
-
-                <div id="admin-page">
-                    <header id="content-header">
-                        <h2 class="pull-left">Site Administration</h2>
-                        <div class="clearfix"></div>
-                    </header>
-
-                    <!-- Admin tab container -->
-                    <div id="admin-config-tabs" class="content-tabs xnat-tab-container">
+    <div id="page-body">
+        <div class="pad">
 
-                        <div class="xnat-nav-tabs side pull-left">
-                            <!-- ================== -->
-                            <!-- Admin tab flippers -->
-                            <!-- ================== -->
-                        </div>
+            <div id="admin-page">
+                <header id="content-header">
+                    <h2 class="pull-left">Site Administration</h2>
+                    <div class="clearfix"></div>
+                </header>
 
-                        <div class="xnat-tab-content side pull-right">
-                            <!-- ================== -->
-                            <!-- Admin tab panes    -->
-                            <!-- ================== -->
-                        </div>
+                <!-- Admin tab container -->
+                <div id="admin-config-tabs" class="content-tabs xnat-tab-container">
 
+                    <div class="xnat-nav-tabs side pull-left">
+                        <!-- ================== -->
+                        <!-- Admin tab flippers -->
+                        <!-- ================== -->
                     </div>
 
-                    <script>
-                        (function () {
-
-                            function fixSlashes(str){
-
-                                var HTTP  = /^http:\/+/;
-                                var HTTPS = /^https:\/+/;
-                                var FTP   = /^ftp:\/+/;
-
-                                // initially remove multiple slashes
-                                str = str.replace(/\/+/g, '/');
-
-                                // if there's a protocol specified,
-                                // restore '//' to that
-                                if (HTTP.test(str)){
-                                    str = str.replace(HTTP, 'http://')
-                                }
-                                else if (HTTPS.test(str)){
-                                    str = str.replace(HTTPS, 'https://')
-                                }
-                                else if (FTP.test(str)){
-                                    str = str.replace(FTP, 'ftp://')
-                                }
-
-                                return str;
-
-                            }
-                            function setUrl(url){
-                                return fixSlashes(PAGE.siteRoot + url);
-                            }
-                            function dataUrl(url){
-                                return setUrl('/page/admin/data/' + url);
-                            }
-
-
-                            loadjs(setUrl('/scripts/lib/yamljs/dist/yaml.js'), function(){
-
-                                // get the JSON and do the setup
-                                $.getJSON(dataUrl('/config/siteAdmin.json')).done(function (data) {
-
-                                    var adminTabs =
-                                            XNAT.ui.tabs
-                                                    .init(data.XNAT)
-                                                    .render('#admin-config-tabs');
-
-                                    console.log(adminTabs);
-                                });
+                    <div class="xnat-tab-content side pull-right">
+                        <!-- ================== -->
+                        <!-- Admin tab panes    -->
+                        <!-- ================== -->
+                    </div>
 
-//                                    $.get({
-//                                        url: dataUrl('config/siteAdminAlt.yaml'),
-//                                        dataType: 'text',
-//                                        success: function (data) {
-//                                            var parsed = YAML.parse(data);
-//                                            console.log(parsed);
-//                                            extend(true, parsed.XNAT.siteAdmin.xnatSetup.siteSetup.siteInfo, {
-//                                                _self: {
-//                                                    foo: 'bar',
-//                                                    fn: function () {
-//                                                        alert('foo')
-//                                                    }
-//                                                },
-//                                                anotherElement: {
-//                                                    _self: {
-//                                                        type: 'thing',
-//                                                        bar: 'foo'
-//                                                    }
-//                                                }
-//                                            });
-//                                            console.log(parsed.XNAT.siteAdmin.xnatSetup.siteSetup.siteInfo._self);
-//
-//                                            parsed.XNAT.siteAdmin.xnatSetup.siteSetup.siteInfo._self.fn();
-//
-//                                        }
-//                                    });
+                </div>
 
-                            });
+                <script src="${sessionScope.siteRoot}/scripts/lib/jquery-plugins/jquery.form.js"></script>
+                <script src="${sessionScope.siteRoot}/scripts/lib/yamljs/dist/yaml.js"></script>
 
-                        })();
-                    </script>
+                <c:import url="/xapi/siteConfig" var="siteConfig"/>
 
-                </div>
+                <script>
+                    XNAT.data = extend({}, XNAT.data, {
+                        siteConfig: ${siteConfig}
+                    });
+                    // get rid of the 'targetSource' property
+                    delete XNAT.data.siteConfig.targetSource;
+                </script>
 
-            </div>
-        </div><!-- /#page-body -->
+                <script src="${sessionScope.siteRoot}/scripts/xnat/ui/templates.js"></script>
+                <script src="${sessionScope.siteRoot}/scripts/xnat/spawner.js"></script>
+                <script src="${sessionScope.siteRoot}/page/admin/tabs.js"></script>
 
-        <div id="page-footer">
-            <div class="pad">
-                <div id="mylogger"></div>
             </div>
-        </div><!-- /#page-footer -->
 
-<div id="sticky-footer">
-    <div>
-        <div class="pad">
-            <a class="xnat-version" target="_blank" href="http://www.xnat.org/" style="">XNAT v1.7</a>
-            <a id="xnat_power" class="pull-right" target="_blank" href="http://www.xnat.org/" style="">
-                <img src="${sessionScope.siteRoot}/images/xnat_power_small.jpg">
-            </a>
         </div>
     </div>
-</div>
+    <!-- /#page-body -->
+
+    <div id="xnat-scripts">
+        <script>
+            //        $(window).load(function(){
+            //            // any values that start with '@|' will be set to
+            //            // the value of the element with matching selector
+            //            $('[value^="@|"]').each(function(){
+            //                var selector = $(this).val().split('@|')[1];
+            //                var value = $$(selector).val();
+            //                $(this).val(value).dataAttr('value',value);
+            //            });
+            //        });
+        </script>
+    </div>
 
-<div id="xnat-scripts">
-    <script>
-        //        $(window).load(function(){
-        //            // any values that start with '@|' will be set to
-        //            // the value of the element with matching selector
-        //            $('[value^="@|"]').each(function(){
-        //                var selector = $(this).val().split('@|')[1];
-        //                var value = $$(selector).val();
-        //                $(this).val(value).dataAttr('value',value);
-        //            });
-        //        });
-    </script>
-</div>
+</pg:restricted>
diff --git a/src/main/webapp/page/admin/data/config/admin-manageData-tab.yaml b/src/main/webapp/page/admin/data/config/admin-manageData-tab.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..b339bcc37b9274dd93c539093c9b1c0f5b3ce5b2
--- /dev/null
+++ b/src/main/webapp/page/admin/data/config/admin-manageData-tab.yaml
@@ -0,0 +1,106 @@
+# Controls and panel defs for the DICOM Connections panel
+
+enableDicomReceiver:
+    label: Enable DICOM Receiver
+    id: enableDicomReceiver
+    name: enableDicomReceiver
+    type: select
+    options:
+        - { label: Yes, value: true }
+        - { label: No, value: false }
+    selected: true
+    description: Caution: Changes to this setting will take effect immediately. Before disabling the receiver, verify that there are no transmissions currently in progress.
+    tooltip: Should the DICOM receiver listen for connections?
+    url: /xapi/services/settings/enableDicomReceiver
+    validation:
+        required: true
+
+dicomHosts:
+    label: DICOM Hosts
+    id: dcmHost
+    name: dcmHost
+    type: hidden
+    url: /xapi/services/settings/dcmHost
+
+dicomAeTitles:
+    label: DICOM AE Titles
+    id: dcmAe
+    name: dcmAe
+    type: text
+    tooltip: These are the SCP Application Entity (AE) title(s).
+    url: /xapi/services/settings/dcmAe
+
+dicomPortNumber:
+    label: DICOM Port Number
+    id: dcmPort
+    name: dcmPort
+    type: text
+    tooltip: This is the port number to which C-STORE SCUs (scanners and other clients sending data) should connect.
+    url: /xapi/services/settings/dcmPort
+
+defaultDicomReceiver:
+    label: Default DICOM Receiver
+    id: servicesDicomScpAetitle
+    name: servicesDicomScpAetitle
+    type: hidden
+    url: /xapi/services/settings/servicesDicomScpAetitle
+    # grab setting for services.dicom.scp.aetitle from services.properties
+    # or, delete if this is obsolete
+
+defaultDicomReceiverUser:
+    label: Default DICOM Receiver User
+    id: servicesDicomScpReceivedfileuser
+    name: servicesDicomScpReceivedfileuser
+    type: hidden
+    url: /xapi/services/settings/servicesDicomScpReceivedfileuser
+    # grab setting for services.dicom.scp.receivedfileuser from services.properties
+    # or, delete if this is obsolete
+
+dicomPanel:
+    label: DICOM Receiver Settings
+    type: panel
+    controls:
+        ${enableDicomReceiver}
+        ${dicomHosts}
+        ${dicomAeTitles}
+        ${dicomPortNumber}
+        ${defaultDicomReceiver}
+        ${defaultDicomReceiverUser}
+
+# Tabs for the Manage Data Tab Group
+
+dicomConnections:
+    label: Manage DICOM Settings
+    type: tab
+    contents:
+        ${dicomPanel}
+        ${petMrPanel}
+
+sessionUploadSettings:
+    label: Session Upload, Import &amp; Anonymization
+    type: tab
+    contents:
+        ${anonScript}
+        ${seriesImportFilter}
+        ${sessionUploadMethod}
+
+dataTypes:
+    label: Manage Data Types
+    type: tab
+    contents:
+        ${dataTypeTable}
+
+manageData:
+    label: Manage Data
+    type: tabGroup
+    tabs:
+        ${dataTypes}
+        ${dicomConnections}
+        ${sessionUploadSettings}
+
+manageDataPage:
+    label: Manage Data
+    type: page
+    contains: tabs
+    contents:
+        ${manageData}
\ No newline at end of file
diff --git a/src/main/webapp/page/admin/data/config/site-admin-sample-new.yaml b/src/main/webapp/page/admin/data/config/site-admin-sample-new.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1624b7eec39865c2397eeef938bcdacfcb98039b
--- /dev/null
+++ b/src/main/webapp/page/admin/data/config/site-admin-sample-new.yaml
@@ -0,0 +1,262 @@
+siteAdmin:
+    kind: tabs
+    name: siteAdmin
+    label: Administer XNAT
+    meta:
+        tabGroups:
+            dashboard: Dashboard
+            xnatSetup: XNAT Setup
+            manageAccess: Manage Access
+            manageData: Manage Data
+            processing: Processing
+            projectCustomization: Project Customization
+            advanced: Advanced XNAT Settings
+            other: Other
+    contains: tabs
+    tabs:  # this property name is the same as 'contains', so it will be treated like 'contents'
+        siteSetup:
+            kind: tab
+            name: siteSetup
+            label: Site Setup
+            group: xnatSetup
+            active: true
+            element:
+                data:
+                    foo: bar
+            contains: panels # the value for 'contains' can be a custom name for 'contents'
+            panels:
+
+                #panel
+                siteInfo:
+                    kind: panel.form
+                    name: siteInfo
+                    label: Site Information
+                    method: POST
+                    action: /xapi/siteConfig/batch
+                    contentType: application/json
+                    load:
+                        lookup: XNAT.data.siteConfig
+                        refresh: /xapi/siteConfig # url to use to get fresh data (uses GET)
+                    contents:
+
+                        # panel input
+                        siteId:
+                            kind: panel.input.text
+                            name: siteId
+                            id: side-id
+                            label: Site ID
+                            value: XNAT
+                            load:
+                                url: /xapi/siteConfig/siteId
+                            description: >
+                                The id used to refer to this site (also used to generate ids). The Site ID must start
+                                with a letter and contain only letters, numbers and underscores. It should be a short,
+                                one-word name or acronym which describes your site.
+                            validation: required id onblur
+
+                        # panel input
+                        adminEmail:
+                            kind: panel.input.email
+                            name: adminEmail
+                            label: Administrator Email Address
+                            url: /xapi/siteConfig/adminEmail
+                            value: ''
+
+                        # panel div
+                        someInfo:
+                            #kind: element
+                            tag: div.message
+                            element:
+                                html: Hello people.
+                                style:
+                                    fontWeight: bold
+                # panel
+                manageThemes:
+                    kind: panel
+                    name: manageThemes
+                    label: Manage Themes
+                    footer: false
+                    contents:
+                        themeUpload:
+                            kind: panel.input.upload
+                            name: themeUpload
+                            # id: uplode-here
+                            method: PUT
+                            action: '/put/the/thing/in/the/stuff'
+                            label: Upload Theme File
+
+
+
+        security:
+            kind: tab
+            name: security
+            label: Security
+            group: xnatSetup
+            contents:
+                generalSecuritySettings:
+                    kind: panel.form
+                    name: generalSecuritySettings
+                    label: General Site Security Settings
+                    contents:
+                        securityChannel:
+                            kind: panel.select.single
+                            name: securityChannel
+                            label: Security Channel
+                            value: https
+                            options:
+                                http:
+                                    # if there's no 'label' property, the value will be used for the label
+                                    label: http
+                                    value: http
+                                https:
+                                    label: https
+                                    value: https
+                            config:
+                                id: security-channel
+                                title: Security Channel
+
+
+        emailServer:
+            kind: tab
+            name: emailServer
+            label: Email Server
+            group: xnatSetup
+            contents:
+                emailServerSettings:
+                    kind: panel.form
+                    method: POST
+#                    action: /xapi/notifications/smtp
+                    action: /xapi/siteConfig/smtpServer
+#                    action: '#@' # this character combo means submit each item individually
+                    contentType: json
+                    load: # load data
+#                        refresh: /xapi/siteConfig/smtpServer
+                        url: /xapi/siteConfig/smtpServer
+                        method: GET      # defaults to GET if not set
+#                        prop: smtpServer # which root property? gets the root object if not set
+#                        data: XNAT.data.siteConfig.smtpServer
+                    name:  emailServerSettings
+                    label: Mail Server Settings
+                    contents:
+                        hostname:
+                            kind: panel.input.text
+                            name: host
+                            label: Host
+                            value: ''
+                            element:
+                                placeholder: localhost
+                            #data:
+                                # METHOD:/url/for/data:object.path
+                                #get: GET|/xapi/siteConfig|smtpServer.host
+                                #set: POST|/xapi/notifications/host
+                        port:
+                            kind: panel.input.number
+                            name: port
+                            label: Port
+                            value: ''
+                            element:
+                                placeholder: 587
+                        username:
+                            kind: panel.input.text
+                            name: username
+                            label: Username
+                            value: ''
+                            element:
+                                placeholder: admin@localhost
+                        password:
+                            kind: panel.input.password
+                            name: password
+                            label: Password
+                            value: ''
+                        protocol:
+                            kind: panel.input.text
+                            name: protocol
+                            label: Protocol
+                            value: ''
+#                            data:
+#                                get: GET|/xapi/siteConfig|smtpServer.protocol
+#                                set: POST|/xapi/notifications/protocol
+
+#                        subhead breaks up panel items
+
+                        mailServerProperties:
+                            kind: panel.subhead
+                            name: mailServerProperties
+                            label: Properties
+                        smtpAuth:
+                            kind: panel.input.checkbox
+                            name: mail.smtp.auth
+                            label: SMTP Auth?
+                            value: true
+                            checked: true
+                        smtpStartTls:
+                            kind: panel.input.checkbox
+                            name: mail.smtp.starttls.enable
+                            label: Smart TLS?
+                            value: true
+                            checked: true
+                        smtpSSLTrust:
+                            kind: panel.input.text
+                            name: mail.smtp.ssl.trust
+                            label: SSL Trust
+                            value: ''
+                            element:
+                                placeholder: localhost
+
+        misc:
+            kind: tab
+            name: misc
+            label: Misc.
+            contents:
+                random:
+                    kind: panel
+                    name: random
+                    id: random-things
+                    label: Random Things
+                    contains: things
+                    things:
+                        panel1:
+                            kind: panel.element
+                            label: A Multi-Select Menu
+                            contents:
+                                select1:
+                                    kind: select.multiple
+                                    label: Select Some:
+                                    options:
+                                        a:
+                                            html: A
+                                            selected: true
+                                        b:
+                                            html: B
+                                            selected: true
+
+                        sectionA:
+                            kind: panel.section
+                            element:
+                                style:
+                                    borderTop: "1px solid #e0e0e0"
+                                    padding: 0 20px
+                                    marginTop: 20px
+                            header: >
+                                <h4>The Button Section</h4>
+                            contains: stuff
+                            stuff:
+                                p1:
+                                    tag: p
+                                    element:
+                                        html: >
+                                            This is a 'panel.section' element. It takes up the entire width
+                                            of the panel and does not have layout structure for a label
+                                            on the left-hand side. There is a 'panel-header' item at the
+                                            top though, which can be set with the 'header' or 'label' property.
+                                            <br><br>
+                                btn1:
+                                    tag: button
+                                    element:
+                                        html: Hello.
+                                    after: <br><br>
+                                btn2:
+                                    tag: button.goodbye
+                                    element:
+                                        html: Goodbye.
+                                    after: <b class="w10">&nbsp;</b><small>Bye-bye.</small>
diff --git a/src/main/webapp/page/admin/data/config/site-setup.yaml b/src/main/webapp/page/admin/data/config/site-setup.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..073f8461e9cc245db4c4f3be44218a52b4169d6c
--- /dev/null
+++ b/src/main/webapp/page/admin/data/config/site-setup.yaml
@@ -0,0 +1,197 @@
+initialSetup:
+    kind: panel.multiForm
+    name: initialSetup
+    label: XNAT Initial Setup
+    #method: POST
+    action: '#!'
+    contents:
+
+        setupMessage:
+            tag: div.message
+            element:
+                style:
+                    marginBottom: 24px
+                html: >
+                    The settings below need to be configured before this XNAT system
+                    can be used. Please set the properties below and submit the form continue.
+
+        # ====================
+        # PANEL
+        siteInfo:
+            kind: panel.form
+            name: siteInfo
+            label: Site Information
+            footer: false
+            method: POST
+            action: /xapi/siteConfig/batch
+            contentType: json
+            load:
+                lookup: XNAT.data.siteConfig
+                refresh: /xapi/siteConfig
+            contents:
+
+                siteId:
+                    kind: panel.input.text
+                    name: siteId
+                    label: Site ID
+                    value: ''
+                    placeholder: XNAT
+                    description: >
+                        The id used to refer to this site (also used to generate ids). The Site ID must start
+                        with a letter and contain only letters, numbers and underscores. It should be a short,
+                        one-word name or acronym which describes your site.
+                    validation: required id
+
+                siteUrl:
+                    kind: panel.input.text
+                    name: siteUrl
+                    label: Site URL
+                    value: ''
+                    placeholder: localhost
+                    description: ''
+                    validation: required url
+
+                adminEmail:
+                    kind: panel.input.email
+                    name: adminEmail
+                    label: Administrator Email Address
+                    value: ''
+                    placeholder: admin@localhost
+                    description: Email address for site administrator.
+                    validation: required email
+
+        # ====================
+        # PANEL
+        dataStorage:
+            kind: panel.form
+            name: dataStorage
+            label: Data Storage
+            footer: false
+            method: POST
+            action: /xapi/siteConfig/batch
+            contentType: json
+            load:
+                lookup: XNAT.data.siteConfig
+                refresh: /xapi/siteConfig
+            contents:
+
+                archiveRootPath:
+                    kind: panel.input.text
+                    name: archiveRootPath
+                    label: Archive Location
+                    size: 50
+                    value: ''
+                    validation: required path
+
+                prearchivePath:
+                    kind: panel.input.text
+                    name: prearchivePath
+                    label: Pre-archive Location
+                    size: 50
+                    value: ''
+                    validation: required path
+
+                cachePath:
+                    kind: panel.input.text
+                    name: cachePath
+                    label: Cache Location
+                    size: 50
+                    value: ''
+                    validation: required path
+
+                buildPath:
+                    kind: panel.input.text
+                    name: buildPath
+                    label: Build Location
+                    size: 50
+                    value: ''
+                    validation: required path
+
+                ftpPath:
+                    kind: panel.input.text
+                    name: ftpPath
+                    label: FTP Location
+                    size: 50
+                    value: ''
+                    validation: required path
+
+                pipelinePath:
+                    kind: panel.input.text
+                    name: pipelinePath
+                    label: Pipeline Installation Location
+                    size: 50
+                    value: ''
+                    validation: required path
+
+        # ====================
+        # PANEL
+        smtpServer:
+            kind: panel.form
+            name: smtpServer
+            label: SMTP Server Settings
+            footer: false
+            method: POST
+            action: /xapi/siteConfig/smtpServer
+            contentType: json
+            load:
+                #lookup: XNAT.data.siteConfig.smtpServer
+                refresh: /xapi/siteConfig/smtpServer
+            contents:
+
+                host:
+                    kind: panel.input.text
+                    name: host
+                    label: Host
+                    value: ?? XNAT.data.siteConfig.smtpServer.host
+                    placeholder: localhost
+
+                port:
+                    kind: panel.input.number
+                    name: port
+                    label: Port
+                    value: ?? XNAT.data.siteConfig.smtpServer.port
+                    placeholder: 25
+
+                username:
+                    kind: panel.input.text
+                    name: username
+                    label: Username
+                    value: ?? XNAT.data.siteConfig.smtpServer.username
+
+                password:
+                    kind: panel.input.password
+                    name: password
+                    label: Password
+                    value: ?? XNAT.data.siteConfig.smtpServer.password
+
+                protocol:
+                    kind: panel.input.text
+                    name: protocol
+                    label: Protocol
+                    value: ?? XNAT.data.siteConfig.smtpServer.protocol
+
+                mailServerProperties:
+                    kind: panel.section
+                    name: mailServerProperties
+                    label: Mail Server Settings
+                    contents:
+
+                        smtpAuth:
+                            kind: panel.input.checkbox
+                            name: mail.smtp.auth
+                            label: SMTP Auth?
+                            value: true
+#                            checked: true
+
+                        smtpStartTls:
+                            kind: panel.input.checkbox
+                            name: mail.smtp.starttls.enable
+                            label: Smart TLS?
+                            value: true
+#                            checked: true
+
+                        smtpSSLTrust:
+                            kind: panel.input.text
+                            name: mail.smtp.ssl.trust
+                            label: SSL Trust
+                            value: ?? XNAT.data.siteConfig.smtpServer['mail.smtp.ssl.trust']
diff --git a/src/main/webapp/page/admin/fake.jsp b/src/main/webapp/page/admin/fake.jsp
new file mode 100644
index 0000000000000000000000000000000000000000..5d63f24f09323bea4e3e452802c48e86fb0c9e6d
--- /dev/null
+++ b/src/main/webapp/page/admin/fake.jsp
@@ -0,0 +1,453 @@
+<%@ 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:if test="${empty hasInit}">
+    <pg:init>
+        <c:if test="${empty hasVars}">
+            <pg:jsvars/>
+        </c:if>
+    </pg:init>
+</c:if>
+
+<script src="${siteRoot}/scripts/lib/jquery-plugins/jquery.dataAttr.js"></script>
+
+<pg:restricted>
+
+<div id="admin-page">
+    <header id="content-header">
+        <h2 class="pull-left">Site Administration</h2>
+        <div class="clearfix"></div>
+    </header>
+
+    <!-- Admin tab container -->
+    <div id="admin-config-tabs" class="content-tabs xnat-tab-container">
+
+
+        <div class="xnat-nav-tabs side pull-left">
+            <!-- ================== -->
+            <!-- Admin tab flippers -->
+            <!-- ================== -->
+            <ul class="nav tab-group" id="dashboard">
+                <li class="label">Dashboard</li>
+                <li class="tab"><a title="Site Dashboard" href="#site-dashboard">Site Dashboard</a></li>
+            </ul>
+            <ul class="nav tab-group" id="xnat-setup">
+                <li class="label">XNAT Setup</li>
+                <li class="tab active"><a title="Site Setup" href="#site-setup">Site Setup</a></li>
+                <li class="tab"><a title="Security" href="#security">Security</a></li>
+                <li class="tab"><a title="Email Server" href="#email-server">Email Server</a></li>
+                <li class="tab"><a title="Notifications" href="#notifications">Notifications</a></li>
+                <li class="tab"><a title="Themes &amp;amp; Features" href="#features">Themes &amp; Features</a></li>
+            </ul>
+            <ul class="nav tab-group" id="manage-access">
+                <li class="label">Manage Access</li>
+                <li class="tab"><a title="Authentication Methods" href="#authentication">Authentication Methods</a></li>
+                <li class="tab"><a title="Users" href="#users">Users</a></li>
+                <li class="tab"><a title="User Roles" href="#user-roles">User Roles</a></li>
+                <li class="tab"><a title="Registration Options" href="#registration">Registration Options</a></li>
+            </ul>
+            <ul class="nav tab-group" id="manage-data">
+                <li class="label">Manage Data</li>
+            </ul>
+            <ul class="nav tab-group" id="processing">
+                <li class="label">Processing</li>
+            </ul>
+            <ul class="nav tab-group" id="project-customization">
+                <li class="label">Project Customization</li>
+            </ul>
+            <ul class="nav tab-group" id="advanced">
+                <li class="label">Advanced XNAT Settings</li>
+                <li class="tab"><a title="DICOM SCP Receivers" href="#dicom-scp-receivers">DICOM SCP Receivers</a></li>
+            </ul>
+        </div>
+        <div class="xnat-tab-content side pull-right">
+            <!-- ================== -->
+            <!-- Admin tab panes    -->
+            <!-- ================== -->
+            <div class="tab-pane" id="site-dashboard-content" style="position: relative; padding-bottom: 60px; display: none;">
+                <div class="pad">
+                    <div class="tab-pane-content"></div>
+                </div>
+                <footer class="footer">
+                    <button class="save-all btn btn-primary pull-right" type="button">Save All</button>
+                </footer>
+            </div>
+            <div class="tab-pane active" id="site-setup-content" style="position: relative; padding-bottom: 60px; display: block;">
+                <div class="pad">
+                    <div class="tab-pane-content">
+                        <form class="xnat-form-panel panel panel-default" id="site-information">
+                            <div class="panel-heading"><h3 class="panel-title">Site Information</h3></div>
+                            <div class="panel-body">
+                                <div class="panel-element" data-name="siteId">
+                                    <label class="element-label" for="site-id">Site ID</label>
+                                    <div class="element-wrapper">
+                                        <input name="siteId" type="text" id="site-id" size="25" title="Site ID">
+                                        <div class="description">The id used to refer to this site (also used to generate database ids). No spaces or non-alphanumeric characters. It should be a short, one-word name or acronym which describes your site.</div>
+                                    </div>
+                                </div>
+                                <div class="panel-element" data-name="siteURL">
+                                    <label class="element-label" for="site-url">Site Url</label>
+                                    <div class="element-wrapper">
+                                        <input name="siteURL" type="text" id="site-url" size="25" title="Site Url">
+                                    </div>
+                                </div>
+                                <div class="panel-element element-group" data-name="siteDescription">
+                                    <label class="element-label" for="site-description">Site Description</label>
+                                    <div class="element-wrapper">
+                                        <div class="group-item" data-name="siteDescriptionType">
+                                            <input name="siteDescriptionType" type="hidden" value="page"></div>
+                                        <div class="group-item" data-name="siteDescription">
+                                            <input name="siteDescription" type="radio" id="site-description-page" value="page" class="dirty"><label for="site-description-page-input"> Page</label><br><input id="site-description-page-input" type="text" name="siteDescriptionPage">
+                                        </div>
+                                        <div class="group-item" data-name="siteDescription">
+                                            <input name="siteDescription" type="radio" id="site-description-text" value="text" class="dirty"><label for="site-description-text-input"> Text (Markdown)</label><br><textarea class="hidden" id="site-description-text-input" name="siteDescriptionText">foo.</textarea>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div class="hidden" data-name="homePage">
+                                    <div class="element-wrapper">
+                                        <input name="homePage" type="hidden" id="home-page" value="/screens/QuickSearch.vm">
+                                    </div>
+                                </div>
+                                <div class="panel-element" data-name="landingPage">
+                                    <label class="element-label" for="landing-page">Landing Page</label>
+                                    <div class="element-wrapper">
+                                        <input name="landingPage" type="text" id="landing-page" size="25" title="Landing Page"><span class="after"><label class="small"><input id="toggle-home-page" type="checkbox" name="setHomePage" class="dirty"> Use This As My Home Page</label></span>
+                                    </div>
+                                </div>
+                                <div class="panel-element" data-name="homePageInput">
+                                    <label class="element-label" for="home-page-input">Home Page</label>
+                                    <div class="element-wrapper">
+                                        <input name="homePageInput" type="text" id="home-page-input" size="25" title="Home Page" data-value="/screens/QuickSearch.vm">
+                                        <script>XNAT.element.copyValue('?homePageInput', '?homePage')</script>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="panel-footer">
+                                <button type="submit" class="save pull-right btn btn-sm btn-primary">Save</button>
+                                <span class="pull-right">&nbsp;&nbsp;&nbsp;</span>
+                                <button type="button" class="revert pull-right btn btn-sm btn-default">Discard Changes</button>
+                                <button type="link" class="defaults pull-left btn btn-sm btn-link">Default Settings</button>
+                                <div class="clear"></div>
+                            </div>
+                        </form>
+                        <form class="xnat-form-panel panel panel-default" id="admin-information">
+                            <div class="panel-heading"><h3 class="panel-title">Admin Information</h3></div>
+                            <div class="panel-body">
+                                <div class="panel-element" data-name="siteAdminEmail">
+                                    <label class="element-label" for="site-admin-email">Site Admin Email</label>
+                                    <div class="element-wrapper">
+                                        <input name="siteAdminEmail" type="text" id="site-admin-email" size="25" title="Site Admin Email">
+                                        <div class="description"><p>
+                                            <a href="/path/to/notification-options">&gt;&gt; Set Email Notification Options</a>
+                                        </p>
+                                            <p>
+                                                <a href="/path/to/email-server-config">&gt;&gt; Setup XNAT Email Server</a>
+                                            </p></div>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="panel-footer">
+                                <button type="submit" disabled="" class="save pull-right btn btn-sm btn-primary disabled">Save</button>
+                                <span class="pull-right">&nbsp;&nbsp;&nbsp;</span>
+                                <button type="button" disabled="" class="revert pull-right btn btn-sm btn-default disabled">Discard Changes</button>
+                                <button type="link" class="defaults pull-left btn btn-sm btn-link">Default Settings</button>
+                                <div class="clear"></div>
+                            </div>
+                        </form>
+                    </div>
+                </div>
+                <footer class="footer">
+                    <button class="save-all btn btn-primary pull-right" type="button">Save All</button>
+                </footer>
+            </div>
+            <div class="tab-pane" id="security-content" style="position: relative; padding-bottom: 60px; display: none;">
+                <div class="pad">
+                    <div class="tab-pane-content">
+                        <form class="xnat-form-panel panel panel-default" id="site-security">
+                            <div class="panel-heading"><h3 class="panel-title">General Site Security Settings</h3></div>
+                            <div class="panel-body">
+                                <div class="panel-element" data-name="securityChannel">
+                                    <label class="element-label" for="security-channel">Security Channel</label>
+                                    <div class="element-wrapper">
+                                        <select name="securityChannel" id="security-channel" title="Security Channel">
+                                            <option value="http">http</option>
+                                            <option value="https">https</option>
+                                        </select></div>
+                                </div>
+                                <div class="panel-element" data-name="requireLogin">
+                                    <label class="element-label" for="require-login">Require User Login?</label>
+                                    <div class="element-wrapper">
+                                        <input name="requireLogin" id="require-login" type="checkbox" title="Require User Login?" class="dirty">
+                                        <div class="description">If checked, then only registered users will be able to access your site. If false, anyone visiting your site will automatically be logged in as 'guest' with access to public data.</div>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="panel-footer">
+                                <button type="submit" class="save pull-right btn btn-sm btn-primary">Save</button>
+                                <span class="pull-right">&nbsp;&nbsp;&nbsp;</span>
+                                <button type="button" class="revert pull-right btn btn-sm btn-default">Discard Changes</button>
+                                <button type="link" class="defaults pull-left btn btn-sm btn-link">Default Settings</button>
+                                <div class="clear"></div>
+                            </div>
+                        </form>
+                        <form class="xnat-form-panel panel panel-default" id="login-session-controls">
+                            <div class="panel-heading"><h3 class="panel-title">User Logins / Session Controls</h3></div>
+                            <div class="panel-body">
+                                <div class="panel-element" data-name="sessionTimeout">
+                                    <label class="element-label" for="session-timeout-input">Session Timeout</label>
+                                    <div class="element-wrapper">
+                                        <input name="sessionTimeout" id="session-timeout-input" type="text" size="3" title="Session Timeout">
+                                        <div class="description">Interval for timing out alias tokens. Uses PostgreSQL interval notation: http://www.postgresql.org/docs/9.0/static/functions-datetime.html</div>
+                                    </div>
+                                </div>
+                                <div class="panel-element" data-name="sessionTimeoutMessage">
+                                    <label class="element-label" for="session-timeout-message">Session Timeout Message</label>
+                                    <div class="element-wrapper">
+                                        <textarea name="sessionTimeoutMessage" id="session-timeout-message" title="Session Timeout Message"></textarea>
+                                        <div class="description">Alert message provided to users after a session timeout and logout.</div>
+                                    </div>
+                                </div>
+                                <div class="panel-element" data-name="sessionAllowResume">
+                                    <label class="element-label" for="session-allow-resume">Allow Resume on Next Login?</label>
+                                    <div class="element-wrapper">
+                                        <input name="sessionAllowResume" id="session-allow-resume" type="checkbox" title="Allow Resume on Next Login?" value="true">
+                                        <div class="description">Allow user to resume where they left off, if logging back in after a session timeout?</div>
+                                    </div>
+                                </div>
+                                <div class="panel-element" data-name="maxSessions">
+                                    <label class="element-label" for="max-sessions">Maximum Concurrent Sessions</label>
+                                    <div class="element-wrapper">
+                                        <input name="maxSessions" type="text" id="max-sessions" size="3" title="Maximum Concurrent Sessions">
+                                        <div class="description">The maximum number of permitted sessions a user can have open simultaneously</div>
+                                    </div>
+                                </div>
+                                <div class="panel-element" data-name="loginFailureMessage">
+                                    <label class="element-label" for="login-failure-message">Login Failure Message</label>
+                                    <div class="element-wrapper">
+                                        <textarea name="loginFailureMessage" id="login-failure-message" title="Login Failure Message"></textarea>
+                                        <div class="description">Text to show when a user fails to login</div>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="panel-footer">
+                                <button type="submit" disabled="" class="save pull-right btn btn-sm btn-primary disabled">Save</button>
+                                <span class="pull-right">&nbsp;&nbsp;&nbsp;</span>
+                                <button type="button" disabled="" class="revert pull-right btn btn-sm btn-default disabled">Discard Changes</button>
+                                <button type="link" class="defaults pull-left btn btn-sm btn-link">Default Settings</button>
+                                <div class="clear"></div>
+                            </div>
+                        </form>
+                    </div>
+                </div>
+                <footer class="footer">
+                    <button class="save-all btn btn-primary pull-right" type="button">Save All</button>
+                </footer>
+            </div>
+            <div class="tab-pane" id="dicom-scp-receivers-content" style="position: relative; padding-bottom: 60px; display: none;">
+                <div class="pad">
+                    <div class="tab-pane-content">
+                        <form class="xnat-form-panel panel panel-default">
+                            <div class="panel-heading"><h3 class="panel-title">Receivers</h3></div>
+                            <div class="panel-body">
+                                <div class="panel-element" data-name="receiverList">
+                                    <label class="element-label" for="receiver-list">Receiver List</label>
+                                    <div class="element-wrapper">
+                                        <table class="input-table">
+                                            <tbody>
+                                            <tr>
+                                                <td><input name="fooInput" data-value="Value 1"></td>
+                                                <td>Text</td>
+                                                <td>Foo</td>
+                                                <td>Bar</td>
+                                            </tr>
+                                            <tr>
+                                                <td><input name="barInput" data-value="1"></td>
+                                                <td>2</td>
+                                                <td>3</td>
+                                                <td>4</td>
+                                            </tr>
+                                            <tr>
+                                                <td><input name="bazInput" data-value="5"></td>
+                                                <td>6</td>
+                                                <td>7</td>
+                                                <td>8</td>
+                                            </tr>
+                                            </tbody>
+                                        </table>
+                                        <script>$('#receiver-list').addClass('foo');</script>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="panel-footer">
+                                <button type="submit" disabled="" class="save pull-right btn btn-sm btn-primary disabled">Save</button>
+                                <span class="pull-right">&nbsp;&nbsp;&nbsp;</span>
+                                <button type="button" disabled="" class="revert pull-right btn btn-sm btn-default disabled">Discard Changes</button>
+                                <button type="link" class="defaults pull-left btn btn-sm btn-link">Default Settings</button>
+                                <div class="clear"></div>
+                            </div>
+                        </form>
+                    </div>
+                </div>
+                <footer class="footer">
+                    <button class="save-all btn btn-primary pull-right" type="button">Save All</button>
+                </footer>
+            </div>
+            <div class="tab-pane" id="email-server-content" style="position: relative; padding-bottom: 60px; display: none;">
+                <div class="pad">
+                    <div class="tab-pane-content">
+                        <form class="xnat-form-panel panel panel-default" id="email-server-config">
+                            <div class="panel-heading"><h3 class="panel-title">Email Server Configuration</h3></div>
+                            <div class="panel-body">
+                                <div class="panel-element" data-name="xdat.mail.server">
+                                    <label class="element-label">Mail Server Host</label>
+                                    <div class="element-wrapper">
+                                        <input name="xdat.mail.server" type="text" size="25" title="Mail Server Host">
+                                    </div>
+                                </div>
+                                <div class="panel-element" data-name="xdat.mail.port">
+                                    <label class="element-label">Mail Server Port</label>
+                                    <div class="element-wrapper">
+                                        <input name="xdat.mail.port" type="text" size="5" title="Mail Server Port">
+                                    </div>
+                                </div>
+                                <div class="panel-element" data-name="xdat.mail.username">
+                                    <label class="element-label">Mail Server Username</label>
+                                    <div class="element-wrapper">
+                                        <input name="xdat.mail.username" type="text" size="25" title="Mail Server Username">
+                                    </div>
+                                </div>
+                                <div class="panel-element" data-name="xdat.mail.password">
+                                    <label class="element-label">Mail Server Password</label>
+                                    <div class="element-wrapper">
+                                        <input name="xdat.mail.password" type="password" title="Mail Server Password">
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="panel-footer">
+                                <button type="submit" disabled="" class="save pull-right btn btn-sm btn-primary disabled">Save</button>
+                                <span class="pull-right">&nbsp;&nbsp;&nbsp;</span>
+                                <button type="button" disabled="" class="revert pull-right btn btn-sm btn-default disabled">Discard Changes</button>
+                                <button type="link" class="defaults pull-left btn btn-sm btn-link">Default Settings</button>
+                                <div class="clear"></div>
+                            </div>
+                        </form>
+                    </div>
+                </div>
+                <footer class="footer">
+                    <button class="save-all btn btn-primary pull-right" type="button">Save All</button>
+                </footer>
+            </div>
+            <div class="tab-pane" id="notifications-content" style="position: relative; padding-bottom: 60px; display: none;">
+                <div class="pad">
+                    <div class="tab-pane-content"></div>
+                </div>
+                <footer class="footer">
+                    <button class="save-all btn btn-primary pull-right" type="button">Save All</button>
+                </footer>
+            </div>
+            <div class="tab-pane" id="features-content" style="position: relative; padding-bottom: 60px; display: none;">
+                <div class="pad">
+                    <div class="tab-pane-content">
+                        <form class="xnat-form-panel panel panel-default" id="theme-config">
+                            <div class="panel-heading"><h3 class="panel-title">Theme Management</h3></div>
+                            <div class="panel-body">
+                                <div class="panel-element" data-name="undefined">
+                                    <div class="element-wrapper">
+                                        <script type="text/javascript" src="../../scripts/themeManagement.js"></script>
+                                    </div>
+                                </div>
+                                <div class="panel-element" data-name="xnat.theme.active">
+                                    <label class="element-label">Current theme</label>
+                                    <div class="element-wrapper"><span title="Current theme">None</span></div>
+                                </div>
+                                <div class="panel-element" data-name="xnat.theme.available">
+                                    <label class="element-label" for="themeSelection">Select an existing theme</label>
+                                    <div class="element-wrapper">
+                                        <select name="xnat.theme.available" id="themeSelection" title="Select an existing theme">
+                                            <option value="null">None</option>
+                                            <option value="Modern UI">Modern UI</option>
+                                            <option value="Green XNAT Theme">Green XNAT Theme</option>
+                                            <option value="Red XNAT Theme">Red XNAT Theme</option>
+                                        </select>&nbsp;<!--button id="submitThemeButton" onclick="setTheme();">Set Theme</button-->&nbsp;&nbsp;
+                                        <button id="removeThemeButton" onclick="removeTheme();">Remove Theme</button>
+                                    </div>
+                                </div>
+                                <div class="panel-element" data-name="xnat.theme.upload">
+                                    <label class="element-label">Upload a theme package</label>
+                                    <div class="element-wrapper">
+                                        <file title="Upload a theme package"></file>
+                                        <form id="uploadThemeForm" method="POST" class="optOutOfXnatDefaultFormValidation" action="/xapi/theme?XNAT_CSRF=undefined">
+                                            <span class="label bold">Upload a theme package: </span><input type="file" id="themeFileUpload" name="themeFileUpload" multiple="" style="width: 270px; display: inline; float: left;">
+                                            <button type="submit" id="submitThemeUploadButton" style="position: relative; top:-15px;">Upload</button>
+                                        </form>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="panel-footer">
+                                <button type="submit" disabled="" class="save pull-right btn btn-sm btn-primary disabled">Save</button>
+                                <span class="pull-right">&nbsp;&nbsp;&nbsp;</span>
+                                <button type="button" disabled="" class="revert pull-right btn btn-sm btn-default disabled">Discard Changes</button>
+                                <button type="link" class="defaults pull-left btn btn-sm btn-link">Default Settings</button>
+                                <div class="clear"></div>
+                            </div>
+                        </form>
+                    </div>
+                </div>
+                <footer class="footer">
+                    <button class="save-all btn btn-primary pull-right" type="button">Save All</button>
+                </footer>
+            </div>
+            <div class="tab-pane" id="authentication-content" style="position: relative; padding-bottom: 60px; display: none;">
+                <div class="pad">
+                    <div class="tab-pane-content"></div>
+                </div>
+                <footer class="footer">
+                    <button class="save-all btn btn-primary pull-right" type="button">Save All</button>
+                </footer>
+            </div>
+            <div class="tab-pane" id="users-content" style="position: relative; padding-bottom: 60px; display: none;">
+                <div class="pad">
+                    <div class="tab-pane-content"></div>
+                </div>
+                <footer class="footer">
+                    <button class="save-all btn btn-primary pull-right" type="button">Save All</button>
+                </footer>
+            </div>
+            <div class="tab-pane" id="user-roles-content" style="position: relative; padding-bottom: 60px; display: none;">
+                <div class="pad">
+                    <div class="tab-pane-content"></div>
+                </div>
+                <footer class="footer">
+                    <button class="save-all btn btn-primary pull-right" type="button">Save All</button>
+                </footer>
+            </div>
+            <div class="tab-pane" id="registration-content" style="position: relative; padding-bottom: 60px; display: none;">
+                <div class="pad">
+                    <div class="tab-pane-content"></div>
+                </div>
+                <footer class="footer">
+                    <button class="save-all btn btn-primary pull-right" type="button">Save All</button>
+                </footer>
+            </div>
+        </div>
+    </div>
+
+    <script>
+//        (function(){
+//            // get the JSON and do the setup
+//            $.getJSON('/page/admin/data/config/site-admin.json').done(function(data){
+//                var adminTabs =
+//                            XNAT.ui.tabs
+//                                .init(data.Result)
+//                                .render('#admin-config-tabs');
+//
+//                console.log(adminTabs);
+//            });
+//        })();
+    </script>
+
+</div>
+
+</pg:restricted>
\ No newline at end of file
diff --git a/src/main/webapp/page/admin/index.jsp b/src/main/webapp/page/admin/index.jsp
index 571b3a48b8d49c71af1da4758b5182feb8d1b9ed..0f46e553c1eac2fbf42c13b78bf52caba0d7fc22 100644
--- a/src/main/webapp/page/admin/index.jsp
+++ b/src/main/webapp/page/admin/index.jsp
@@ -5,16 +5,9 @@
 <c:set var="pageName" value="admin" scope="request"/>
 
 <pg:wrapper>
+    <pg:xnat>
 
-    <pg:head title="Site Admin"/>
-
-    <pg:content id="${pageName}" className="xnat app ${pageName}">
-        <div id="page-wrapper">
-            <div class="pad">
-                <jsp:include page="content.jsp"/>
-            </div>
-        </div>
-        <!-- /#page-wrapper -->
-    </pg:content>
+        <jsp:include page="${not empty param.view ? param.view : 'content'}.jsp"/>
 
+    </pg:xnat>
 </pg:wrapper>
diff --git a/src/main/webapp/page/admin/info/content.jsp b/src/main/webapp/page/admin/info/content.jsp
index 0945e2df4b314177bdcaad7e8ebd20f8ca7eb804..282eb405913a031ccdd4cda01a1215e9c1385408 100644
--- a/src/main/webapp/page/admin/info/content.jsp
+++ b/src/main/webapp/page/admin/info/content.jsp
@@ -3,60 +3,67 @@
 <%@ taglib prefix="pg" tagdir="/WEB-INF/tags/page" %>
 
 <c:if test="${empty hasInit}">
-    <pg:init/>
+    <pg:init>
+        <c:if test="${empty hasVars}">
+            <pg:jsvars/>
+        </c:if>
+    </pg:init>
 </c:if>
 
-<c:choose>
-    <c:when test="${isAdmin == true}">
-        <h1>Some info</h1>
-
-        <c:import url="/xapi/users.json" var="userList"/>
-
-        <h2>Users</h2>
-        <p id="user-list-raw">
-                ${userList}
-        </p>
-        <p id="user-list"></p>
-        <script>
-            +function(){
-                var userList = JSON.parse('${userList}');
-                var ul = spawn.element('ul#users');
-                $.each(userList, function(i, name){
-                    ul.appendChild(spawn.element('li', name))
-                });
-                $('#user-list').append(ul);
-            }()
-        </script>
-
-        <br>
-
-        <h2>Me</h2>
-        <ul>
-            <li><b>Logged in:</b> ${loggedIn} </li>
-            <li><b>Username:</b> ${username} </li>
-            <li><b>Is Admin?:</b> ${isAdmin} </li>
-            <li><b>CSRF token:</b> ${csrfToken} </li>
-            <li><b>Landing page:</b> ${landingPage} </li>
-        </ul>
-
-        <br>
-
-        <div id="page-info">
-            <p>request: ${requestScope.javamelody.request}</p>
-            <p>sessionScope: <br>${sessionScope}</p>
-            <p>applicationScope: <br> ${applicationScope}</p>
-            <p>requestScope: <br> ${requestScope}</p>
-            <p>pageScope: <br> ${pageScope}</p>
-            <p>pageContext: <br> ${pageContext}</p>
-            <p>param: <br> ${param}</p>
-            <p>paramValues: <br> ${paramValues}</p>
-            <p>header: <br> ${header}</p>
-            <p>headerValues: <br> ${headerValues}</p>
-            <p>cookie: <br> ${cookie}</p>
-            <p>SPRING_SECURITY_LAST_USERNAME: <br> ${SPRING_SECURITY_LAST_USERNAME}</p>
-        </div>
-    </c:when>
-    <c:otherwise>
-        <p>(not authorized)</p>
-    </c:otherwise>
-</c:choose>
+<c:set var="pageName" value="info" scope="request"/>
+
+<c:set var="_msg">
+    Nope.
+</c:set>
+
+<pg:restricted msg="${_msg}">
+
+    <h1>Some info</h1>
+
+    <c:import url="/xapi/users.json" var="userList"/>
+
+    <h2>Users</h2>
+    <p id="user-list-raw">
+            ${userList}
+    </p>
+    <p id="user-list"></p>
+    <script>
+        +function(){
+            var userList = JSON.parse('${userList}');
+            var ul = spawn.element('ul#users');
+            $.each(userList, function(i, name){
+                ul.appendChild(spawn.element('li', name))
+            });
+            $('#user-list').append(ul);
+        }()
+    </script>
+
+    <br>
+
+    <h2>Me</h2>
+    <ul>
+        <li><b>Logged in:</b> ${loggedIn} </li>
+        <li><b>Username:</b> ${username} </li>
+        <li><b>Is Admin?:</b> ${isAdmin} </li>
+        <li><b>CSRF token:</b> ${csrfToken} </li>
+        <li><b>Landing page:</b> ${landingPage} </li>
+    </ul>
+
+    <br>
+
+    <div id="page-info">
+        <p>request: ${requestScope.javamelody.request}</p>
+        <p>sessionScope: <br>${sessionScope}</p>
+        <p>applicationScope: <br> ${applicationScope}</p>
+        <p>requestScope: <br> ${requestScope}</p>
+        <p>pageScope: <br> ${pageScope}</p>
+        <p>pageContext: <br> ${pageContext}</p>
+        <p>param: <br> ${param}</p>
+        <p>paramValues: <br> ${paramValues}</p>
+        <p>header: <br> ${header}</p>
+        <p>headerValues: <br> ${headerValues}</p>
+        <p>cookie: <br> ${cookie}</p>
+        <p>SPRING_SECURITY_LAST_USERNAME: <br> ${SPRING_SECURITY_LAST_USERNAME}</p>
+    </div>
+
+</pg:restricted>
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..9acf00fb6d9b9723cc42474f69693de4dca8964d
--- a/src/main/webapp/page/admin/info/index.jsp
+++ b/src/main/webapp/page/admin/info/index.jsp
@@ -1,30 +1,11 @@
 <%@ 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="info"/>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
 
 <pg:wrapper>
+    <pg:xnat>
 
-    <pg:head>
-        <jsp:attribute name="headTop">
-            <!-- headTop -->
-        </jsp:attribute>
-
-        <!-- custom content for head element -->
-
-        <jsp:attribute name="headBottom">
-            <!-- headBottom -->
-        </jsp:attribute>
-    </pg:head>
-
-    <pg:content id="${pageName}" className="xnat app ${pageName}">
-        <div id="page-wrapper">
-            <div class="pad">
-                <jsp:include page="content.jsp"/>
-            </div>
-        </div>
-        <!-- /#page-wrapper -->
-    </pg:content>
+        <jsp:include page="content.jsp"/>
 
+    </pg:xnat>
 </pg:wrapper>
diff --git a/src/main/webapp/page/admin/site-config/content.jsp b/src/main/webapp/page/admin/site-config/content.jsp
new file mode 100644
index 0000000000000000000000000000000000000000..9103383d7f304d3b10f1e109c1b6e92bcd9a2513
--- /dev/null
+++ b/src/main/webapp/page/admin/site-config/content.jsp
@@ -0,0 +1,125 @@
+<%@ 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:if test="${empty hasInit}">
+    <pg:init>
+        <c:if test="${empty hasVars}">
+            <pg:jsvars/>
+        </c:if>
+    </pg:init>
+</c:if>
+
+<c:set var="_msg">
+    Nope.
+</c:set>
+
+<pg:restricted msg="${_msg}">
+
+    <c:import url="/xapi/siteConfig" var="siteConfig"/>
+
+    <script>
+        XNAT.data = extend({}, XNAT.data, {
+            siteConfig: ${siteConfig}
+        });
+        // get rid of the 'targetSource' property
+        delete XNAT.data.siteConfig.targetSource;
+    </script>
+
+    <div id="page-body">
+        <div class="pad" style="padding:20px;">
+
+            <header id="content-header">
+                <h2 class="pull-left">XNAT Site Config Output</h2>
+                <div class="clear"></div>
+            </header>
+
+            <hr class="light">
+
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">View Site Config Items</h3>
+                </div>
+                <div class="panel-body">
+                    <div class="panel-element">
+                        <label for="select-site-config-item" class="element-label">Select a property</label>
+                        <div class="element-wrapper">
+
+                            <select id="select-site-config-item" style="width:350px;">
+                                <option value="!"> &nbsp; </option>
+                            </select>
+
+                            <br><br>
+
+                            <textarea id="site-config-item" class="mono" style="width:400px;height:200px;"></textarea>
+
+                            <hr class="light">
+
+                            <button type="button" id="show-site-config-json">Show Site Config JSON</button>
+
+                            <form method="" action=""></form>
+                            <div class="description"></div>
+
+                        </div>
+                    </div>
+                </div>
+                <div class="hidden"></div>
+            </div>
+
+
+        </div>
+    </div>
+
+    <script>
+        (function(){
+
+            var siteConfigObj = XNAT.data.siteConfig;
+            var $siteConfigSelect = $('#select-site-config-item');
+            var siteConfigItems = '';
+
+            // populate the menu
+            forOwn(siteConfigObj, function(prop, val){
+                // having trouble with objects right now
+                // if (isPlainObject(val)) return;
+                siteConfigItems += ('<option value="' + prop + '">' + prop + '</div> \n');
+            });
+
+            $siteConfigSelect.append(siteConfigItems);
+
+            // select an item to show in the textarea
+            $siteConfigSelect.on('change', function(){
+                if (this.value === '!') return false;
+                var val = siteConfigObj[this.value];
+                if (isPlainObject(val) || Array.isArray(val)) {
+                    val = JSON.stringify(val, null, 4);
+                }
+                $('#site-config-item').text(val);
+                // don't need to do a REST call because we already have the object
+//                $.get(XNAT.url.restUrl('/xapi/siteConfig/' + this.value), function(data){
+//                    $('#site-config-item').val(data)
+//                });
+            });
+
+            // show the whole siteConfig object in an xmodal dialog
+            $('#show-site-config-json').click(function(){
+                xmodal.open({
+                    size: 'large',
+                    minWidth: 800,
+                    minHeight: '90%',
+                    //width: 600,
+                    //height: 500,
+                    maximize: true,
+                    title: 'Site Config JSON',
+                    content: prettifyJSON(siteConfigObj)
+                });
+            });
+
+            $(function(){
+                $siteConfigSelect.chosen();
+            })
+
+        })();
+    </script>
+
+</pg:restricted>
+
diff --git a/src/main/webapp/page/admin/site-config/index.jsp b/src/main/webapp/page/admin/site-config/index.jsp
new file mode 100644
index 0000000000000000000000000000000000000000..9acf00fb6d9b9723cc42474f69693de4dca8964d
--- /dev/null
+++ b/src/main/webapp/page/admin/site-config/index.jsp
@@ -0,0 +1,11 @@
+<%@ page session="true" contentType="text/html" pageEncoding="UTF-8" language="java" %>
+<%@ taglib prefix="pg" tagdir="/WEB-INF/tags/page" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+
+<pg:wrapper>
+    <pg:xnat>
+
+        <jsp:include page="content.jsp"/>
+
+    </pg:xnat>
+</pg:wrapper>
diff --git a/src/main/webapp/page/admin/site-config/siteConfig.js b/src/main/webapp/page/admin/site-config/siteConfig.js
new file mode 100644
index 0000000000000000000000000000000000000000..8f7b17c5b26fa51367ebeb531b350b3cc8fec8e4
--- /dev/null
+++ b/src/main/webapp/page/admin/site-config/siteConfig.js
@@ -0,0 +1,63 @@
+// prepare the form when the DOM is ready
+$(document).ready(function() {
+    var options = {
+        target:        '#output2',   // target element(s) to be updated with server response
+        beforeSubmit:  showRequest,  // pre-submit callback
+        success:       showResponse  // post-submit callback
+
+        // other available options:
+        //url:       url         // override for form's 'action' attribute
+        //type:      type        // 'get' or 'post', override for form's 'method' attribute
+        //dataType:  null        // 'xml', 'script', or 'json' (expected server response type)
+        //clearForm: true        // clear all form fields after successful submit
+        //resetForm: true        // reset the form after successful submit
+
+        // $.ajax options can be used here too, for example:
+        //timeout:   3000
+    };
+
+    // bind to the form's submit event
+    $('#myForm2').submit(function() {
+        // inside event callbacks 'this' is the DOM element so we first
+        // wrap it in a jQuery object and then invoke ajaxSubmit
+        $(this).ajaxSubmit(options);
+
+        // !!! Important !!!
+        // always return false to prevent standard browser submit and page navigation
+        return false;
+    });
+});
+
+// pre-submit callback
+function showRequest(formData, jqForm, options) {
+    // formData is an array; here we use $.param to convert it to a string to display it
+    // but the form plugin does this for you automatically when it submits the data
+    var queryString = $.param(formData);
+
+    // jqForm is a jQuery object encapsulating the form element.  To access the
+    // DOM element for the form do this:
+    // var formElement = jqForm[0];
+
+    alert('About to submit: \n\n' + queryString);
+
+    // here we could return false to prevent the form from being submitted;
+    // returning anything other than false will allow the form submit to continue
+    return true;
+}
+
+// post-submit callback
+function showResponse(responseText, statusText, xhr, $form)  {
+    // for normal html responses, the first argument to the success callback
+    // is the XMLHttpRequest object's responseText property
+
+    // if the ajaxSubmit method was passed an Options Object with the dataType
+    // property set to 'xml' then the first argument to the success callback
+    // is the XMLHttpRequest object's responseXML property
+
+    // if the ajaxSubmit method was passed an Options Object with the dataType
+    // property set to 'json' then the first argument to the success callback
+    // is the json data object returned by the server
+
+    alert('status: ' + statusText + '\n\nresponseText: \n' + responseText +
+        '\n\nThe output div should have already been updated with the responseText.');
+}
diff --git a/src/main/webapp/page/admin/spawner/content.jsp b/src/main/webapp/page/admin/spawner/content.jsp
new file mode 100644
index 0000000000000000000000000000000000000000..11d0a5b141e0c80ea2f208fa88cc6a09cb5c9f94
--- /dev/null
+++ b/src/main/webapp/page/admin/spawner/content.jsp
@@ -0,0 +1,47 @@
+    <%@ 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" %>
+        <%--<%@ taglib prefix="sp" tagdir="/WEB-INF/tags/spawner" %>--%>
+
+        <c:set var="MSG">
+            No spawning allowed.
+        </c:set>
+
+        <pg:restricted msg="${MSG}">
+
+            <c:set var="_siteRoot" value="${sessionScope.siteRoot}"/>
+
+            <div class="panel panel-default">
+
+            <div class="panel-heading">
+            <h3 class="panel-title">XNAT Spawner Elements</h3>
+            </div>
+
+            <div class="panel-body">
+
+            <div data-name="spawnerElements" class="panel-element">
+
+            <%--<label class="element-label" for="!?"></label>--%>
+            <%--<div class="element-wrapper">--%>
+
+            <table id="spawner-element-list" class="xnat-table alt1 clean" style="width:100%">
+            <!-- list of available namespaces will show here -->
+            </table>
+
+            <div class="description" style="margin:20px 5px 0">View and manage XNAT Spawner elements.</div>
+
+            <%--</div>--%>
+
+            </div>
+            </div>
+
+            <div class="hidden"></div>
+
+            </div>
+
+            <!-- button element will be rendered in this span -->
+            <span id="view-json"></span>
+
+            <script src="${_siteRoot}/page/admin/spawner/spawner-admin.js"></script>
+
+        </pg:restricted>
diff --git a/src/main/webapp/page/admin/spawner/index.jsp b/src/main/webapp/page/admin/spawner/index.jsp
new file mode 100644
index 0000000000000000000000000000000000000000..97d43bfa2f352332a3d0ef8c66a37ce6690d7ac8
--- /dev/null
+++ b/src/main/webapp/page/admin/spawner/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="spawner" scope="request"/>
+
+<pg:wrapper>
+    <pg:xnat title="Manage Spawner">
+
+        <jsp:include page="content.jsp"/>
+
+    </pg:xnat>
+</pg:wrapper>
diff --git a/src/main/webapp/page/admin/spawner/spawner-admin.js b/src/main/webapp/page/admin/spawner/spawner-admin.js
new file mode 100644
index 0000000000000000000000000000000000000000..4b63c32a39cb3e8726ff5cc2c4b46e7ce085367b
--- /dev/null
+++ b/src/main/webapp/page/admin/spawner/spawner-admin.js
@@ -0,0 +1,167 @@
+(function(){
+
+    function showJSON(json){
+        return xmodal.message({
+            title: 'Site Admin JSON',
+            maximize: true,
+            width: '90%',
+            height: '90%',
+            content: spawn('pre.json', JSON.stringify(json, null, 2)).outerHTML
+        })
+    }
+
+    spawn('button|type=button', {
+        html: 'View JSON',
+        onclick: function(){
+            XNAT.xhr.get(XNAT.url.rootUrl('/xapi/spawner/resolve/siteAdmin/siteAdmin'), function(data){
+                showJSON(data);
+            });
+        },
+        $: {
+            appendTo: '#view-json'
+        }
+    });
+
+    //$('#view-json').click(function(){});
+
+})();
+
+XNAT.xhr.getJSON({
+    url: XNAT.url.rootUrl('/xapi/spawner/namespaces'),
+    success: function(json){
+
+        var items = [];
+
+        $.each(json, function(){
+
+            var NS = this;
+            var tds = [];
+
+            tds.push(['td', '<b>' + NS + '</b>']);
+            tds.push(['td', [
+                ['button.link', {
+                    type: 'button',
+                    title: XNAT.url.rootUrl('/xapi/spawner/elements/' + NS),
+                    html: 'View Raw Elements',
+                    onclick: function(e){
+                        e.preventDefault();
+                        XNAT.xhr.get(this.title, function(data){
+                            xmodal.open({
+                                size: 'med',
+                                maximize: true,
+                                content: '<pre>'+JSON.stringify(data, null, 2)+'</pre>'
+                            });
+                        });
+                    }
+                }],
+                ['span', '&nbsp;'],
+                ['button', {
+                    type: 'button',
+                    title: XNAT.url.rootUrl('/xapi/spawner/resolve/' + NS + '/' + NS),
+                    html: 'View Resolved Elements',
+                    onclick: function(e){
+                        e.preventDefault();
+                        XNAT.xhr.get(this.title, function(data){
+                            xmodal.open({
+                                size: 'med',
+                                maximize: true,
+                                content: '<pre>'+JSON.stringify(data, null, 2)+'</pre>'
+                            });
+                        });
+                    }
+                }]
+            ]]);
+
+            var idsMenu = spawn('select#spawner-ns-ids', [['option|value=!', 'Select an Element']]);
+
+            tds.push(['td', [
+
+                idsMenu,
+
+                ['span', '&nbsp;'],
+
+                ['button', {
+                    type: 'button',
+                    html: 'View Selected Element',
+                    onclick: function(){
+                        var getId = $('#spawner-ns-ids').val();
+                        if (!getId || getId === '!') {
+                            xmodal.message('Select an Element ID');
+                            return false;
+                        }
+                        var elementUrl = XNAT.url.rootUrl('/xapi/spawner/element/' + NS + '/' + getId);
+                        $.get(elementUrl, function(data){
+                            var _textarea = spawn('textarea.mono', {
+                                name: getId,
+                                html: data.yaml,
+                                style: { width: '500px', height: '250px' }
+                            });
+                            _textarea.innerHTML = (data.yaml);
+                            var _table = XNAT.table({className: 'xnat-table borderless'}).init([
+                                ['<b class="label">Namespace:</b> ', data.namespace],
+                                ['<b class="label">Element ID:</b> ', data.elementId],
+                                [ [['b.label', 'Label: ']], data.label ],
+                                //['<b class="label">Label:</b> ', data.label],
+                                [ [['b.label', 'Created: ']], new Date(data.created).toString() ],
+                                // ['<b class="label">Created:</b> ', new Date(data.created).toString()],
+                                ['<b class="label">Modified:</b> ', new Date(data.timestamp).toString()]
+                            ]);
+                            // anothe way to add a row of data
+                            _table.tr().td([['b.label', 'YAML: ']]).td([_textarea]);
+                            //_table.tr().td('<b>YAML:</b> ').td([_textarea]);
+                            xmodal.open({
+                                //size: 'med',
+                                width: 700,
+                                height: 550,
+                                maximize: true,
+                                title: 'Element ID: ' + getId,
+                                content: _table.get().outerHTML,
+                                beforeShow: function(obj){
+                                    console.log(obj)
+                                },
+                                okLabel: 'Save Changes',
+                                okAction: function(){
+                                    XNAT.xhr.put({
+                                        // url: XNAT.url.csrfUrl(elementUrl),
+                                        url: (elementUrl),
+                                        data: {
+                                            // namespace: data.namespace,
+                                            // elementId: data.elementId,
+                                            yaml: _textarea.value
+                                        },
+                                        //dataType: 'text/x-yaml',
+                                        //contentType: 'application/json',
+                                        contentType: 'text/x-yaml',
+                                        processData:  false,
+                                        success: function(){
+                                            xmodal.message('Data saved.')
+                                        },
+                                        error: function(e){
+                                            xmodal.message('An error occured: ' + e)
+                                        }
+                                    });
+                                }
+                            })
+                        })
+                    }
+                }]
+            ]]);
+
+            // spawn and push the row
+            items.push(spawn('tr', tds));
+
+            (function(){
+                XNAT.xhr.get(XNAT.url.rootUrl('/xapi/spawner/ids/' + NS), function(ids){
+                    $.each(ids, function(){
+                        var id = this;
+                        idsMenu.appendChild(spawn('option', { value: id, html: id }))
+                    });
+                });
+            })()
+
+        });
+
+        $('#spawner-element-list').append(items);
+
+    }
+});
diff --git a/src/main/webapp/page/admin/style.css b/src/main/webapp/page/admin/style.css
new file mode 100644
index 0000000000000000000000000000000000000000..436bbaf55d01a86e78e31011856020b2d7e7e4f0
--- /dev/null
+++ b/src/main/webapp/page/admin/style.css
@@ -0,0 +1,236 @@
+/**
+    XNAT Admin UI custom styles
+*/
+
+#page_wrapper { overflow: auto; }
+
+.xnat-nav-tabs { position: relative; z-index: 1; }
+.xnat-nav-tabs li {
+    float: left; margin-right: -1px; margin-bottom: -1px;
+    padding: 0;
+    border: 1px solid #e0e0e0;
+    box-sizing: border-box;
+}
+.xnat-nav-tabs li:hover { background: #f0f0f0; }
+.xnat-nav-tabs li > a {
+    display: block;
+    padding: 8px 18px;
+    font-size: 15px; color: #444;
+    outline: none !important;
+    background: #f0f0f0 top repeat-x url('data:image/gif;base64,R0lGODlhBAAiAKIAAPf39/Hx8fX19fPz8/Dw8PLy8vT09Pb29iH5BAAAAAAALAAAAAAEACIAAAMhCLp6/jDKJ6q9WJjNu/dDKI7kUJxoqqpB675wQMx0bRMJADs=');
+    background-image: linear-gradient( #f8f8f8, #f0f0f0 );
+}
+.xnat-nav-tabs li > a:active { /*outline: none !important;*/ }
+.xnat-nav-tabs li > a:hover { background: inherit; }
+.xnat-nav-tabs li.active { background: #2fa4e7; border-color: #e0e0e0; z-index: 2; }
+.xnat-nav-tabs li.active > a { color: #000; background: #fff !important; }
+.xnat-nav-tabs li.active > a:hover { background: inherit; cursor: default; }
+
+/* TOP TABS */
+.xnat-nav-tabs.top {  }
+.xnat-nav-tabs.top li { border-bottom: none; }
+.xnat-nav-tabs.top li > a { border-bottom: 1px solid #e0e0e0; }
+.xnat-nav-tabs.top li.active > a {
+    top: 5px; padding-top: 3px; padding-bottom: 13px;
+    border-bottom-color: #fff;
+}
+
+/* LEFT-SIDE TABS */
+.xnat-nav-tabs.side { width: 25%; right: -1px; }
+.xnat-nav-tabs.side li { width: 100%; border-right: none; }
+.xnat-nav-tabs.side li > a { border-right: 1px solid #e0e0e0; }
+.xnat-nav-tabs.side li.active { /* border-bottom-color: #d0d0d0; */ }
+.xnat-nav-tabs.side li.active > a { padding-left: 12px; padding-right: 24px; border-left: 6px solid #2fa4e7; border-right-color: #fff; }
+/*.xnat-tab-content.side.pull-right .panel { padding: 24px; }*/
+/* (if we ever need right-side tabs, new style rules will be needed ) */
+
+/* TAB GROUPS */
+.xnat-nav-tabs.side .tab-group { display: none; margin-top: 20px; padding: 0; list-style-type: none; }
+.xnat-nav-tabs.side .tab-group li.label {
+    padding: 10px 2px; border: none;  text-align: left;
+    font-size: 13px; font-weight: bold; color: #000; }
+.xnat-nav-tabs.side .tab-group li.label:hover { background: inherit; }
+.xnat-nav-tabs.side .tab-group:first-of-type { margin-top: 0; }
+.xnat-nav-tabs.side .tab-group:first-of-type li.label { padding-top: 0; }
+
+/* TAB CONTENT AND PANES */
+.xnat-tab-content {
+    padding: 20px 20px 0; margin-bottom: 20px;
+    background: #fff; border: 1px solid #e0e0e0;
+    box-sizing: border-box;
+}
+.xnat-tab-content .tab-pane { display: none; min-height: 500px; height: auto; overflow: auto; background: #fff; }
+.xnat-tab-content .tab-pane > .pad { margin-bottom: 20px; padding: 20px 20px 0 20px; }
+.xnat-tab-content .tab-pane > .footer {
+    width: 100%; position: absolute; bottom: 0; padding: 10px 15px; overflow: auto;
+    background: #f0f0f0; border-top: 1px solid #e0e0e0; }
+.xnat-tab-content .tab-pane.active { display: block; }
+.xnat-tab-content.side { width: 75%; }
+
+/* PANELS */
+body.xnat .panel { margin-bottom: 20px; background: #f8f8f8; border-radius: 0; }
+body.xnat .panel-default { border: 1px solid #e0e0e0; }
+.panel-default .panel-heading {
+    padding: 12px 16px; font-size: 15px;
+    color: #222; background: #e9e9e9; border-color: #d0d0d0 ;
+}
+.panel-default .panel-body { padding: 12px; }
+.panel-default .panel-footer  { padding: 12px; background: #f0f0f0; }
+.panel-title { font-weight: bold; font-size: 15px; line-height: inherit; }
+
+/* PANEL ELEMENTS */
+.panel .panel-element { margin: 15px 0; clear: both; overflow: auto; }
+.panel .panel-element > * { box-sizing: border-box; }
+.panel .panel-element .element-label {
+    width: 35%; padding-right: 10px; float: left;
+    color: #222; font-weight: bold; text-align: right;
+    line-height: 24px; vertical-align: middle;
+}
+.panel .panel-element .element-wrapper { display: inline-block; width: 65%; float: right; }
+.panel .panel-element .element-wrapper .description { margin: 10px 0; }
+.panel .panel-element span.before { right: 10px; }
+.panel .panel-element span.after { margin-left: 10px; }
+.panel .panel-element label.small { font-weight: normal; }
+
+.panel .panel-subhead {
+    padding: 2px; margin: 10px;
+    font-size: 13px;
+    border-bottom: 1px solid #e0e0e0;
+}
+
+/* ELEMENT GROUP ITEMS */
+.panel .panel-element-group .group-item .element-label { width: auto; }
+
+/* MULTI-FORM PANEL ELEMENTS */
+.multi-form.panel {}
+.multi-form.panel > .panel-heading {  }
+.multi-form.panel > .panel-heading .panel-title { font-weight: normal; font-size: 18px; }
+.multi-form.panel > .panel-body { padding: 24px 24px 0; background: #fff; }
+.multi-form.panel > .panel-footer {  }
+/* MULTI-FORM PANEL CHILD PANELS */
+.multi-form.panel > .panel-body > .panel .panel-title {  }
+
+/* FORM CONTROLS */
+body.xnat textarea.form-control { font-family: Courier, monospace; font-weight: normal; }
+
+/* ==========================================================
+     UI WIDGETS
+  ========================================================== */
+.input-table { border-collapse: collapse; }
+.input-table td { padding: 5px; border: 1px solid #e0e0e0; }
+.input-table input { border: 1px solid #e0e0e0; }
+
+
+/* CONFIG PANELS */
+.config.panel .panel-footer { text-align: right; }
+.config.panel .panel-footer button { margin-left: 10px; }
+
+
+/*
+CSS styles for gradients with
+embedded base-64 image fallbacks
+*/
+
+/* greater difference between top and bottom values */
+/* used for .panel-heading background */
+.cssgradients .vgradient-lt-gray,
+.cssgradients .gradient-lt-gray,
+.cssgradients .gradient-light-gray,
+.cssgradients body.xnat .panel-default .panel-heading {
+    /* background: #e9e9e9 url('images/gradient-light-gray-34px.gif') top repeat-x; */
+    background: #e9e9e9 top repeat-x url('data:image/gif;base64,R0lGODlhBAAiAMQAAPz8/Pf39+vr6/X19fLy8vHx8fv7++np6fb29vPz8/Dw8P39/fr6+urq6vT09O7u7vj4+Ozs7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAAAAAALAAAAAAEACIAAAU74CKOImACRqqmTOu2UCzPcWAHSK7nQz84wCAwQSwSCUhCYclcKp7Q52NKrU4j2KwWK+gKGuAw+EAuk0MAOw==');
+    background-image: linear-gradient( #fdfdfd, #e9e9e9 );
+}
+
+/* subtle difference between top and bottom values */
+/* used for .nav-tab background */
+.cssgradients .vgradient-lt-gray2,
+.cssgradients .gradient-lt-gray2,
+.cssgradients .gradient-light-gray2 {
+    /* background: #f0f0f0 url('images/gradient-light-gray2-34px.gif') top repeat-x; */
+    background: #f0f0f0 top repeat-x url('data:image/gif;base64,R0lGODlhBAAiAKIAAPf39/Hx8fX19fPz8/Dw8PLy8vT09Pb29iH5BAAAAAAALAAAAAAEACIAAAMhCLp6/jDKJ6q9WJjNu/dDKI7kUJxoqqpB675wQMx0bRMJADs=');
+    background-image: linear-gradient( #f8f8f8, #f0f0f0 );
+}
+
+
+.clearfix:before,
+.clearfix:after,
+.container:before,
+.container:after,
+.container-fluid:before,
+.container-fluid:after,
+.row:before,
+.row:after,
+.form-horizontal .form-group:before,
+.form-horizontal .form-group:after,
+.btn-toolbar:before,
+.btn-toolbar:after,
+.btn-group-vertical > .btn-group:before,
+.btn-group-vertical > .btn-group:after,
+.nav:before,
+.nav:after,
+.navbar:before,
+.navbar:after,
+.navbar-header:before,
+.navbar-header:after,
+.navbar-collapse:before,
+.navbar-collapse:after,
+.pager:before,
+.pager:after,
+.panel-body:before,
+.panel-body:after,
+.modal-footer:before,
+.modal-footer:after {
+    content: " ";
+    display: table;
+}
+.clearfix:after,
+.container:after,
+.container-fluid:after,
+.row:after,
+.form-horizontal .form-group:after,
+.btn-toolbar:after,
+.btn-group-vertical > .btn-group:after,
+.nav:after,
+.navbar:after,
+.navbar-header:after,
+.navbar-collapse:after,
+.pager:after,
+.panel-body:after,
+.modal-footer:after {
+    clear: both;
+}
+.center-block {
+    display: block;
+    margin-left: auto;
+    margin-right: auto;
+}
+.pull-right {
+    float: right !important;
+}
+.pull-left {
+    float: left !important;
+}
+.hide {
+    display: none !important;
+}
+.show {
+    display: block !important;
+}
+.invisible {
+    visibility: hidden;
+}
+.text-hide {
+    font: 0/0 a;
+    color: transparent;
+    text-shadow: none;
+    background-color: transparent;
+    border: 0;
+}
+.hidden {
+    display: none !important;
+    visibility: hidden !important;
+}
+.affix {
+    position: fixed;
+}
\ No newline at end of file
diff --git a/src/main/webapp/page/admin/tabs.js b/src/main/webapp/page/admin/tabs.js
new file mode 100644
index 0000000000000000000000000000000000000000..209daed94bd6a3ec471b05e548f688fd98c613dc
--- /dev/null
+++ b/src/main/webapp/page/admin/tabs.js
@@ -0,0 +1,51 @@
+(function(){
+
+    var $head = $('head');
+
+     // // append the css to the head
+     // $head.spawn('link', {
+     //     rel: 'stylesheet',
+     //     type: 'text/css',
+     //     href: XNAT.url.rootUrl('/scripts/lib/bootstrap/themes/xnat/bootstrap-fixed.css')
+     // });
+ 
+     // append the css to the head
+     $head.spawn('link', {
+         rel: 'stylesheet',
+         type: 'text/css',
+         href: XNAT.url.rootUrl('/page/admin/style.css')
+     });
+
+    // get the JSON and do the setup
+//    var jsonUrl = XNAT.url.rootUrl('/page/admin/data/config/site-admin-sample-new.yaml');
+    //var jsonUrl = XNAT.url.rootUrl('/page/admin/data/config/site-admin-sample-new.json');
+    var jsonUrl = XNAT.url.rootUrl('/xapi/spawner/resolve/siteAdmin/siteAdmin');
+    
+    // get the siteConfig object first
+    // doing this in JSP for better(?) performance
+    //XNAT.xhr.get(XNAT.url.restUrl('/xapi/siteConfig'), function(siteConfig){
+
+    // put those values in an object named XNAT.data.siteConfig
+    //  XNAT.data = extend({}, XNAT.data, {
+    //    siteConfig: siteConfig
+    //});
+
+    // THEN render the tabs...
+    $.get({
+        url: jsonUrl,
+        // dataType: 'text',
+        success: function(data){
+            var json = data;
+            if(typeof data === 'string'){
+                json = YAML.parse(data);
+						}
+
+            console.log(JSON.stringify(data, ' ', 1));
+
+						var adminTabs = XNAT.spawner.spawn(json);
+            adminTabs.render('#admin-config-tabs > .xnat-tab-content');
+						XNAT.app.adminTabs = adminTabs;
+						// xmodal.loading.closeAll();
+				}
+		});
+})();
diff --git a/src/main/webapp/page/admin/tabs.jsp b/src/main/webapp/page/admin/tabs.jsp
new file mode 100644
index 0000000000000000000000000000000000000000..72496896871412aba4518b211cb2ad199ffbbe36
--- /dev/null
+++ b/src/main/webapp/page/admin/tabs.jsp
@@ -0,0 +1,39 @@
+<%@ 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:if test="${empty hasInit}">
+    <pg:init>
+        <c:if test="${empty hasVars}">
+            <pg:jsvars/>
+        </c:if>
+    </pg:init>
+</c:if>
+
+<pg:restricted>
+
+    <div id="admin-page">
+        <header id="content-header">
+            <h2 class="pull-left">Site Administration</h2>
+            <div class="clearfix"></div>
+        </header>
+
+        <!-- Admin tab container -->
+        <div id="admin-config-tabs" class="content-tabs xnat-tab-container">
+            <div class="xnat-nav-tabs side pull-left">
+                <!-- ================== -->
+                <!-- Admin tab flippers -->
+                <!-- ================== -->
+            </div>
+            <div class="xnat-tab-content side pull-right">
+                <!-- ================== -->
+                <!-- Admin tab panes    -->
+                <!-- ================== -->
+            </div>
+        </div>
+
+        <%--<script src="${sessionScope.siteRoot}/scripts/xnat/ui/tabs.js"></script>--%>
+        <script src="${sessionScope.siteRoot}/page/admin/tabs.js"></script>
+
+    </div>
+</pg:restricted>
diff --git a/src/main/webapp/page/admin/ui.jsp b/src/main/webapp/page/admin/ui.jsp
new file mode 100644
index 0000000000000000000000000000000000000000..132ca10e51c71393f44532af5e71b3a57c65aa34
--- /dev/null
+++ b/src/main/webapp/page/admin/ui.jsp
@@ -0,0 +1,68 @@
+<%@ 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:if test="${empty hasInit}">
+    <pg:init>
+        <c:if test="${empty hasVars}">
+            <pg:jsvars/>
+        </c:if>
+    </pg:init>
+</c:if>
+
+<pg:restricted>
+
+    <div id="admin-page">
+        <header id="content-header">
+            <h2 class="pull-left">Site Administration</h2>
+            <div class="clearfix"></div>
+        </header>
+
+        <!-- Admin tab container -->
+        <div id="admin-config-tabs" class="content-tabs xnat-tab-container">
+            <div class="xnat-nav-tabs side pull-left">
+                <!-- ================== -->
+                <!-- Admin tab flippers -->
+                <!-- ================== -->
+            </div>
+            <div class="xnat-tab-content side pull-right">
+                <!-- ================== -->
+                <!-- Admin tab panes    -->
+                <!-- ================== -->
+            </div>
+        </div>
+
+        <script>
+            (function(){
+
+                var $head = $('head');
+
+//                // append the css to the head
+//                $head.spawn('link', {
+//                    rel: 'stylesheet',
+//                    type: 'text/css',
+//                    href: XNAT.url.rootUrl('/scripts/lib/bootstrap/themes/xnat/bootstrap-fixed.css')
+//                });
+
+                // append the css to the head
+                $head.spawn('link', {
+                    rel: 'stylesheet',
+                    type: 'text/css',
+                    href: XNAT.url.rootUrl('/page/admin/style.css')
+                });
+
+                // get the JSON and do the setup
+                var jsonUrl = XNAT.url.rootUrl('/page/admin/data/config/site-admin.json');
+                $.getJSON(jsonUrl).done(function(data){
+                    var adminTabs =
+                                XNAT.ui.tabs
+                                    .init(data.Result)
+                                    .render('#admin-config-tabs');
+
+                    console.log(adminTabs);
+                });
+            })();
+        </script>
+
+    </div>
+</pg:restricted>
diff --git a/src/main/webapp/page/blank/content.jsp b/src/main/webapp/page/blank/content.jsp
new file mode 100755
index 0000000000000000000000000000000000000000..862624f3bf7fea2df2e561f44d9ddc81ac898a98
--- /dev/null
+++ b/src/main/webapp/page/blank/content.jsp
@@ -0,0 +1,22 @@
+<%@ 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:if test="${empty hasInit}">
+    <pg:init>
+        <c:if test="${empty hasVars}">
+            <pg:jsvars/>
+        </c:if>
+    </pg:init>
+</c:if>
+
+<div id="page-wrapper">
+    <div class="pad">
+
+        <h1>${pageName}</h1>
+
+        <p>Stuff goes here.</p>
+
+    </div>
+</div>
+<!-- /#page-wrapper -->
diff --git a/src/main/webapp/page/blank/index.jsp b/src/main/webapp/page/blank/index.jsp
new file mode 100755
index 0000000000000000000000000000000000000000..19be0e55733bf6fe581aebd38202f35a0706c976
--- /dev/null
+++ b/src/main/webapp/page/blank/index.jsp
@@ -0,0 +1,21 @@
+<%@ 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="blank" scope="request"/>
+
+<c:set var="_headTop">
+    <script>alert('(it is blank)')</script>
+</c:set>
+
+<c:set var="_bodyBottom">
+    <h1>I'm at the bottom.</h1>
+</c:set>
+
+<pg:wrapper>
+    <pg:xnat headTop="${_headTop}" bodyBottom="${_bodyBottom}">
+
+        <jsp:include page="content.jsp"/>
+
+    </pg:xnat>
+</pg: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..e7deaccfcba687842739815e27ced60d4862dfe4
--- a/src/main/webapp/page/content.jsp
+++ b/src/main/webapp/page/content.jsp
@@ -3,7 +3,11 @@
 <%@ taglib prefix="pg" tagdir="/WEB-INF/tags/page" %>
 
 <c:if test="${empty hasInit}">
-    <pg:init/>
+    <pg:init>
+        <c:if test="${empty hasVars}">
+            <pg:jsvars/>
+        </c:if>
+    </pg:init>
 </c:if>
 
 <div id="page-wrapper">
@@ -12,34 +16,17 @@
     </div>
 </div>
 
-
 <script>
-    (function () {
-
-        function getUrlHash(key) {
-            var hash = window.location.hash.split(key || '#')[1] || '';
-            return hash.split('/#')[0];
-        }
+    (function(){
 
-        function getContent() {
-            var hash = getUrlHash('#/') || 'home';
-            XNAT.xhr.get({
-                dataType: 'html',
-                url: PAGE.pageRoot + '/' + (hash.replace(/\/+$/, '')) + '/content.jsp',
-                success: function (html) {
-                    $('#page-content').html(html);
-                },
-                failure: function () {
-                    $('#page-content').html('page not found');
-                }
-            })
-        }
+        var customPage = XNAT.app.customPage;
+        var $pageContent = $('#page-content');
 
-        getContent();
+        customPage.getPage('', $pageContent);
 
-        window.onhashchange = function () {
-            getContent();
-        }
+//        window.onhashchange = function(){
+//            customPage.getPage('', $pageContent);
+//        }
 
     })();
-</script>
\ No newline at end of file
+</script>
diff --git a/src/main/webapp/page/foo/content.jsp b/src/main/webapp/page/foo/content.jsp
index 11127ca60a8017daf6335a5cb41ec7592146bb7a..6af56631fbad46c1c3bf727fef8b2100c6f9e41e 100644
--- a/src/main/webapp/page/foo/content.jsp
+++ b/src/main/webapp/page/foo/content.jsp
@@ -3,7 +3,11 @@
 <%@ taglib prefix="pg" tagdir="/WEB-INF/tags/page" %>
 
 <c:if test="${empty hasInit}">
-    <pg:init/>
+    <pg:init>
+        <c:if test="${empty hasVars}">
+            <pg:jsvars/>
+        </c:if>
+    </pg:init>
 </c:if>
 
 <h1>FOO</h1>
diff --git a/src/main/webapp/page/home/content.jsp b/src/main/webapp/page/home/content.jsp
index 449ace8e37c990097e1b3f9766d02b75aa4a2a26..3bcb79d573aab684fbc23451eb7fc5afd57fecd6 100644
--- a/src/main/webapp/page/home/content.jsp
+++ b/src/main/webapp/page/home/content.jsp
@@ -3,7 +3,11 @@
 <%@ taglib prefix="pg" tagdir="/WEB-INF/tags/page" %>
 
 <c:if test="${empty hasInit}">
-    <pg:init/>
+    <pg:init>
+        <c:if test="${empty hasVars}">
+            <pg:jsvars/>
+        </c:if>
+    </pg:init>
 </c:if>
 
 <h1>Home</h1>
diff --git a/src/main/webapp/page/home/index.jsp b/src/main/webapp/page/home/index.jsp
index 1170879bab944c83d71c29e00215b55f45182310..40f28b268e547a2e8f194df0a6059ba726ec1033 100644
--- a/src/main/webapp/page/home/index.jsp
+++ b/src/main/webapp/page/home/index.jsp
@@ -2,21 +2,8 @@
 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
 <%@ taglib prefix="pg" tagdir="/WEB-INF/tags/page" %>
 
-<c:set var="pageName" value="home" scope="request"/>
-
 <pg:wrapper>
-
-    <pg:head>
-        <!-- custom content for head element -->
-    </pg:head>
-
-    <pg:content id="${pageName}" className="xnat app ${pageName}">
-        <div id="page-wrapper">
-            <div class="pad">
-                <jsp:include page="content.jsp"/>
-            </div>
-        </div>
-        <!-- /#page-wrapper -->
-    </pg:content>
-
+    <pg:xnat>
+        <jsp:include page="content.jsp"/>
+    </pg:xnat>
 </pg:wrapper>
diff --git a/src/main/webapp/page/index.jsp b/src/main/webapp/page/index.jsp
old mode 100644
new mode 100755
index 151538a3fdb1aab098f6fe4b550e5eeefdc745d4..d52a01fcbf975711a8fab553125c133188534b97
--- a/src/main/webapp/page/index.jsp
+++ b/src/main/webapp/page/index.jsp
@@ -2,14 +2,16 @@
 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
 <%@ taglib prefix="pg" tagdir="/WEB-INF/tags/page" %>
 
-<c:set var="pageName" value="view-page" scope="request"/>
-
 <pg:wrapper>
+    <pg:xnat>
+
+        <c:set var="view" value="${param.view}"/>
 
-    <pg:head/>
+        <c:if test="${empty view}">
+            <c:set var="view" value="content"/>
+        </c:if>
 
-    <pg:content id="${pageName}" className="xnat app ${pageName}">
-        <jsp:include page="content.jsp"/>
-    </pg:content>
+        <jsp:include page="${view}.jsp"/>
 
+    </pg:xnat>
 </pg:wrapper>
diff --git a/src/main/webapp/page/spawn/content.jsp b/src/main/webapp/page/spawn/content.jsp
new file mode 100644
index 0000000000000000000000000000000000000000..2027be6c3d04d6c158e5916e6a4dc8fbdd1a324f
--- /dev/null
+++ b/src/main/webapp/page/spawn/content.jsp
@@ -0,0 +1,52 @@
+<%@ 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" %>
+<%--<%@ taglib prefix="sp" tagdir="/WEB-INF/tags/spawner" %>--%>
+
+<c:if test="${empty hasInit}">
+    <pg:init>
+        <c:if test="${empty hasVars}">
+            <pg:jsvars/>
+        </c:if>
+    </pg:init>
+</c:if>
+
+<c:set var="MSG">
+    Playground clossed.
+</c:set>
+
+<pg:restricted msg="${MSG}">
+
+    <c:set var="_siteRoot" value="${sessionScope.siteRoot}"/>
+
+    <!-- playground for XNAT.spawner methods -->
+
+    <a id="show-spawn" class="link" href="#!">Show some spawn() examples below &gt;&gt;</a>
+
+    <div id="spawn-container"></div>
+
+    <script src="${_siteRoot}/scripts/lib/spawn/spawn.examples.js"></script>
+    <script src="${_siteRoot}/scripts/xnat/spawner.js"></script>
+
+    <script>
+
+        (function(){
+
+            var $showSpawn = $('#show-spawn');
+            var $spawnContainer = $('#spawn-container');
+
+            $showSpawn.click(function(e){
+                e.preventDefault();
+                $spawnContainer.spawn('hr.clear', {
+                    style: 'margin: 30px 0'
+                }, '<br>');
+                $spawnContainer.append(spawn.examples.createSpawn());
+            });
+
+        })();
+
+
+
+    </script>
+
+</pg:restricted>
\ No newline at end of file
diff --git a/src/main/webapp/page/spawn/index.jsp b/src/main/webapp/page/spawn/index.jsp
new file mode 100644
index 0000000000000000000000000000000000000000..5659c18f4be818d4533ac704e92e4ad1e3ddf430
--- /dev/null
+++ b/src/main/webapp/page/spawn/index.jsp
@@ -0,0 +1,11 @@
+<%@ 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" %>
+
+<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/globals.js b/src/main/webapp/scripts/globals.js
index 42efbd168c7b2dde85c1fc35e946481b5cffd9fa..032b900554c3159b71372375641b0b55db78261e 100644
--- a/src/main/webapp/scripts/globals.js
+++ b/src/main/webapp/scripts/globals.js
@@ -270,7 +270,7 @@ function extendCopyDeep(){
 
 // return a cloned copy of a single 'obj'
 function cloneObject(obj){
-    return extend(true, {}, obj);
+    return extend(true, {}, obj || {});
 }
 
 // add child objects to 'obj' object from string
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/jquery-plugins/jquery.dataAttr.js b/src/main/webapp/scripts/lib/jquery-plugins/jquery.dataAttr.js
index c002e2878696ae007d9d77d864d4158633a64326..0728d1217b0ab52533844aa14dc7cac97a15ec28 100644
--- a/src/main/webapp/scripts/lib/jquery-plugins/jquery.dataAttr.js
+++ b/src/main/webapp/scripts/lib/jquery-plugins/jquery.dataAttr.js
@@ -16,7 +16,7 @@
  * jQuery().data().name values override
  * element.dataset.name properties.
  * @param {string} name - Name of [data-] attribute
- * @param {string|array|object|function} value - The value to assign to name
+ * @param {string|array|object|function} [value] - The value to assign to name
  * @return {object}
  * Returns object map of data attributes and values
  * OR the jQuery object .dataAttr() was called on
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/themeManagement.js b/src/main/webapp/scripts/themeManagement.js
index 4e8d49929663710e6f765a2281773647d01082ab..677ea79cd99ed6a1708d65a5968a6bbea43102f9 100644
--- a/src/main/webapp/scripts/themeManagement.js
+++ b/src/main/webapp/scripts/themeManagement.js
@@ -15,9 +15,9 @@ var csrf = 'XNAT_CSRF='+window.csrfToken;
 $('#titleAppName').text(XNAT.app.siteId);
 var currentTheme = $('#currentTheme');
 var themeSelector = $('#themeSelection');
-var uploadForm = document.getElementById('uploadThemeForm');
-var themeUploader = document.getElementById('themeFileUpload');
-var themeUploadSubmit = document.getElementById('submitThemeUploadButton');
+var uploadForm = document.getElementById('themeFileUpload-form');
+var themeUploader = document.getElementById('themeFileUpload-input');
+var themeUploadSubmit = document.getElementById('themeFileUpload-button');
 var selectedTheme = null;
 function populateThemes(){
     getCurrentTheme(getAvailableThemes);
diff --git a/src/main/webapp/scripts/xmodal-v1/xmodal.css b/src/main/webapp/scripts/xmodal-v1/xmodal.css
index 32057f9b94e74485b849234e367127c0b756b8ec..9ad44b973e18834927d72bf9e1f824832e3208fa 100644
--- a/src/main/webapp/scripts/xmodal-v1/xmodal.css
+++ b/src/main/webapp/scripts/xmodal-v1/xmodal.css
@@ -4,55 +4,54 @@
 
 /*html.noscroll { position: relative ; overflow-y: scroll ; width: 100% ; }*/
 
-body.xmodal-v1 { display: block !important ; overflow-y: scroll ; } /* the body will not disappear! */
+body.xmodal-v1, body.xmodal { display: block !important ; overflow-y: scroll ; } /* the body will not disappear! */
 /*body.xmodal-v1.open { position: fixed !important ; overflow: hidden ; } *//* hold the page still while the xModal is open */
 
-div.xmodal-mask.v1 { display: none ; margin: auto ; position: fixed ; left: 0 ; top: 0 ; width: 100% ; height: 100% ; background: rgba(0,0,0,0.3) ; }
-div.xmodal-mask.v1 { background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABhJREFUeNpiZGBg8GVAAkwMaICwAECAAQAUlABVw7mtDgAAAABJRU5ErkJggg==); }
-/*div.xmodal-mask.v1.open { display: block ; z-index: 100 ; }*/
+div.xmodal-mask { display: none ; margin: auto ; position: fixed ; left: 0 ; top: 0 ; width: 100% ; height: 100% ; background: rgba(0,0,0,0.3) ; }
+div.xmodal-mask { background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABhJREFUeNpiZGBg8GVAAkwMaICwAECAAQAUlABVw7mtDgAAAABJRU5ErkJggg==); }
+/*div.xmodal-mask.open { display: block ; z-index: 100 ; }*/
 
-div.xmodal.v1 {
+div.xmodal {
     display: none ; min-width: 200px ; min-height: 128px ; max-width: 98% ; max-height: 98% ;
     position: fixed ; top: 0 ; right: 0 ; bottom: 0 ; left: 0 ; margin: auto ; overflow: hidden ;
     background: #fff ; border: 1px solid #a0a0a0 ; box-shadow: 0 10px 30px rgba(0,0,0,0.5) ;
     font-family: Arial, Helvetica, sans-serif ; }
 
-div.xmodal.v1.open { display: block; }
+div.xmodal.open { display: block; }
 
-div.xmodal.v1:focus { outline: 1px solid #808080; }
+div.xmodal:focus { outline: 1px solid #808080; }
 
-div.xmodal.v1.dialog { width: 600px ; height: 400px ; }
+div.xmodal.dialog { width: 600px ; height: 400px ; }
 
-div.xmodal.v1.loading { min-height: 80px; }
+div.xmodal.loading { min-height: 80px; }
 
-div.xmodal.v1.maxxed {
+div.xmodal.maxxed {
     width: 98% !important; height: 98% !important;
     max-width: 98% !important; max-height: 98% !important;
     top: 0 !important; right: 0 !important;
     bottom: 0 !important; left: 0 !important; }
 
-div.xmodal.v1 .button:hover,
-div.xmodal.v1 .close:hover { cursor: pointer ; }
+div.xmodal .button:hover,
+div.xmodal .close:hover { cursor: pointer ; }
 
-div.xmodal.v1 .buttons :focus { box-shadow: 0 0 5px rgba(0, 100, 200, 0.8); }
+div.xmodal .buttons :focus { box-shadow: 0 0 5px rgba(0, 100, 200, 0.8); }
 
-div.xmodal.v1 .title {
-    height: 25px ; padding: 0 ; overflow: hidden ; line-height: inherit ;
-    color: #303030 ; background: #f0f0f0 center center repeat-x ; border-bottom: 1px solid #c0c0c0 ; }
-div.xmodal.v1 .title { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAkCAIAAAAhPwwfAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADBJREFUeNpi/PPnDwMMMDEgAYo5LP///yeZ8+/fP2JkyFFGDodiF5ARBhQHPECAAQDfTksARZ3oFgAAAABJRU5ErkJggg==); }
-div.xmodal.v1 .title .inner {
+div.xmodal .title {
+    height: 40px; padding: 0 ; overflow: hidden ;
+    color: #303030 ; background: #f0f0f0 ; border-bottom: 1px solid #e0e0e0 ; }
+div.xmodal .title .inner {
     display: inline-block; padding-left: 10px ; overflow: hidden; white-space: nowrap;
-    font-size: 12px ; font-weight: bold ; line-height: 28px ; vertical-align: middle ; }
-div.xmodal.v1 .title .inner > i { color: #505050; font-weight: normal; font-style: normal; }
-div.xmodal.v1 .title .close,
-div.xmodal.v1 .title .maximize {
+    font-size: 15px ; font-weight: normal ; line-height: 42px ; vertical-align: middle ; }
+div.xmodal .title .inner > i { color: #505050; font-weight: normal; font-style: normal; }
+div.xmodal .title .close,
+div.xmodal .title .maximize {
     width: 24px; height: 16px; margin: 0; padding: 0;
     position: absolute ; top: 4px ; right: 4px ;
     font-size: 16px ; font-weight: bold ; text-align: center; line-height: 17px;
     color: #606060 ; background: #f8f8f8 ;
     border: 1px solid #b0b0b0 ; border-radius: 2px; }
-div.xmodal.v1 .title .maximize { right: 34px; line-height: 10px; }
-div.xmodal.v1 .title .maximize:hover { cursor: pointer; }
+div.xmodal .title .maximize { right: 34px; line-height: 10px; }
+div.xmodal .title .maximize:hover { cursor: pointer; }
 
 /* base64 close-yui.gif */
 /*div.xmodal .title .close {*/
@@ -60,49 +59,47 @@ div.xmodal.v1 .title .maximize:hover { cursor: pointer; }
     /*background-image: url(data:image/gif;base64,R0lGODlhGQAPALMJALGxsZ2dnezs7HNzc21tbYWFhfX19cHBwf///////wAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAkALAAAAAAZAA8AAAROMJ1Jq71Tos27/9sEjqMIAgZnAOVxEkVqFAT7md4MC3QM4jnaAJb6uUaC4UBAAnZ0Q58RpCvwiNNPAKsLZHOBIsIQ/pLOCCf6J8G4MYkIADs=);*/
 /*}*/
 
-div.xmodal.v1 .body { width: 100% ; height: 100% ; position: absolute ; overflow: hidden ; }
-div.xmodal.v1 .body.scroll { overflow-y: auto ; }
-div.xmodal.v1 .body .inner { display: none ; padding: 20px ; font-size: 13px ; line-height: 17px ; }
-div.xmodal.v1.nopad .body .inner { padding: 0 ; }
-div.xmodal.v1.open .body .inner { display: block ; }
-div.xmodal.v1 .body h1,
-div.xmodal.v1 .body h2,
-div.xmodal.v1 .body h3,
-div.xmodal.v1 .body h4 { margin-bottom: 1em !important; }
-div.xmodal.v1 .body p { margin: 0 0 1em ; }
-
-div.xmodal.v1 .footer {
-    display: block ; width: 100% ; /*height: 52px ;*/ margin-top: -1px ; padding: 0 ;
+div.xmodal .body { width: 100% ; height: 100% ; position: absolute ; overflow: hidden ; }
+div.xmodal .body.scroll { overflow-y: auto ; }
+div.xmodal .body .inner { display: none ; padding: 20px ; font-size: 13px ; line-height: 17px ; }
+div.xmodal.nopad .body .inner { padding: 0 ; }
+div.xmodal.open .body .inner { display: block ; }
+div.xmodal .body h1,
+div.xmodal .body h2,
+div.xmodal .body h3,
+div.xmodal .body h4 { margin-bottom: 1em !important; }
+div.xmodal .body p { margin: 0 0 1em ; }
+
+div.xmodal .footer {
+    display: block ; width: 100% ; height: 60px ; margin-top: -1px ; padding: 0 ;
     position: absolute ; bottom: 0 ;
     background: #f0f0f0; border-top: 1px solid #e0e0e0 ; overflow: hidden ; }
-div.xmodal.v1 .footer.white { background: #fff ; border-top-color: #fff ; }
-div.xmodal.v1 .footer .inner { display: table ; width: 100% ; height: 100% ; position: relative ; }
-div.xmodal.v1 .footer .content { display: table-cell ; padding-left: 15px ; vertical-align: middle ; text-align: left ; }
-div.xmodal.v1 .footer .buttons { display: table-cell ; padding-right: 15px ; vertical-align: middle ; text-align: right ; }
-div.xmodal.v1 .footer .buttons .link { display: inline-block; padding: 10px; font-size: 13px; text-decoration: underline; }
-div.xmodal.v1 .footer .button {
+div.xmodal .footer.white { background: #fff ; border-top-color: #fff ; }
+div.xmodal .footer .inner { display: table ; width: 100% ; height: 100% ; position: relative ; }
+div.xmodal .footer .content { display: table-cell ; padding-left: 15px ; vertical-align: middle ; text-align: left ; }
+div.xmodal .footer .buttons { display: table-cell ; padding-right: 15px ; vertical-align: middle ; text-align: right ; }
+div.xmodal .footer .buttons .link { display: inline-block; padding: 10px; font-size: 13px; text-decoration: underline; }
+div.xmodal .footer .button {
     display: inline-block ; margin-left: 10px ; padding: 1px 15px !important ; float: right ;
     line-height: 23px ; vertical-align: middle ; font-size: 13px ; font-weight: bold ; text-decoration: none !important ; }
-div.xmodal.v1 .footer a.button { height: 24px ; line-height: 24px; } /* set height only for <a> buttons */
-div.xmodal.v1 .footer .button,
-div.xmodal.v1 .footer .button.cancel.default {
-    color: #303030 ; background: #e0e0e0 center center repeat-x ; border: 1px solid #a0a0a0 ; border-radius: 3px ;
-    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAeCAIAAACE3ijUAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADlJREFUeNpi/P//PwMMMDEgAYo5LP/+/SOG8+fPHxwyf//+xcFB0YNbGW49uO3B7VAUZVQNN4AAAwDARzjNaWlUCgAAAABJRU5ErkJggg==); }
-div.xmodal.v1 .footer .button:active,
-div.xmodal.v1 .footer .button.cancel.default:active { color: inherit ; background: #d0d0d0 }
-div.xmodal.v1 .footer .button.default { font-weight: bold ; color: #fff ; background: #07428d center top repeat-x ; border-color: #293d66 ;
-    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAeCAIAAACE3ijUAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAFJJREFUeNqMzsENwDAIA0BH6nrdKbt1rGLTfiqcRyr4nWwB45wXvjmUKND0QoWgJeG14Lb2kxjuXm3ZtvxG7r6WaPAFqXCwA2QTdgdITwqPAAMAHoRQTInXYu8AAAAASUVORK5CYII=); }
-div.xmodal.v1 .footer .button.default:active { background: #084FAB ; border-color: #243356 ; }
-div.xmodal.v1 .footer .button.hidden,
-div.xmodal.v1 .footer .button.disabled { /*display: inline-block ;*/ color: #f0f0f0 ; background: #d0d0d0 ; border-color: #c0c0c0 ; }
-div.xmodal.v1 .footer .button.hidden:hover,
-div.xmodal.v1 .footer .button.disabled:hover { cursor: not-allowed !important ; background: #d0d0d0 ; }
-
-div.xmodal.v1.loading .title .close { display: none ; }
-div.xmodal.v1.loading .body .inner { text-align: center ; }
-
-.ie8 body div.xmodal.v1 .footer .button { /* margin: 10px ; */ padding: 3px 6px ; }
-.ie9 body div.xmodal.v1 .footer .button { /* margin: 12px ; */ padding: 4px 6px 3px ; }
+div.xmodal .footer a.button { height: 24px ; line-height: 24px; } /* set height only for <a> buttons */
+div.xmodal .footer .button,
+div.xmodal .footer .button.cancel.default {
+    color: #303030 ; background: #e0e0e0 center center repeat-x ; border: 1px solid #a0a0a0 ; border-radius: 3px ; }
+div.xmodal .footer .button:active,
+div.xmodal .footer .button.cancel.default:active { color: inherit ; background: #d0d0d0 }
+div.xmodal .footer .button.default { font-weight: bold ; color: #fff ; background: #07428d center top repeat-x ; border-color: #293d66 ; }
+div.xmodal .footer .button.default:active { background: #084FAB ; border-color: #243356 ; }
+div.xmodal .footer .button.hidden,
+div.xmodal .footer .button.disabled { /*display: inline-block ;*/ color: #f0f0f0 ; background: #d0d0d0 ; border-color: #c0c0c0 ; }
+div.xmodal .footer .button.hidden:hover,
+div.xmodal .footer .button.disabled:hover { cursor: not-allowed !important ; background: #d0d0d0 ; }
+
+div.xmodal.loading .title .close { display: none ; }
+div.xmodal.loading .body .inner { text-align: center ; }
+
+.ie8 body div.xmodal .footer .button { /* margin: 10px ; */ padding: 3px 6px ; }
+.ie9 body div.xmodal .footer .button { /* margin: 12px ; */ padding: 4px 6px 3px ; }
 
 .modal_content, .modal_template { display: none ; }
 
@@ -115,4 +112,4 @@ for db.humanconnectome.org
 /* custom form styles from HCP didn't work for core XNAT */
 
 
-div.xmodal.v1.embedded { top: 0; left: 0; border: none; box-shadow: none; display: block; z-index: auto;  }
+div.xmodal.embedded { top: 0; left: 0; border: none; box-shadow: none; display: block; z-index: auto;  }
diff --git a/src/main/webapp/scripts/xnat/spawner.js b/src/main/webapp/scripts/xnat/spawner.js
new file mode 100644
index 0000000000000000000000000000000000000000..ccff298b5c976ae424efdb6618bd4d646e1621f4
--- /dev/null
+++ b/src/main/webapp/scripts/xnat/spawner.js
@@ -0,0 +1,201 @@
+/*!
+ * Spawn UI elements using the Spawner service
+ */
+
+var XNAT = getObject(XNAT);
+
+(function(factory){
+    if (typeof define === 'function' && define.amd) {
+        define(factory);
+    }
+    else if (typeof exports === 'object') {
+        module.exports = factory();
+    }
+    else {
+        return factory();
+    }
+}(function(){
+
+    var undefined,
+        ui, spawner,
+        NAMESPACE  = 'XNAT.ui',
+        $          = jQuery || null, // check and localize
+        hasConsole = console && console.log;
+
+    XNAT.ui =
+        getObject(XNAT.ui || {});
+    XNAT.spawner = spawner =
+        getObject(XNAT.spawner || {});
+
+    // keep track of items that spawned
+    spawner.spawnedElements = [];
+
+    // keep track of items that didn't spawn
+    spawner.notSpawned = [];
+
+
+    // ==================================================
+    // MAIN FUNCTION
+    spawner.spawn = function _spawn(obj){
+
+        var frag  = document.createDocumentFragment(),
+            $frag = $(frag);
+
+        forOwn(obj, function(item, prop){
+
+            var kind, method, spawnedElement, $spawnedElement;
+            
+            // save the config properties in a new object
+            prop = getObject(prop);
+
+            prop.config = prop.config || prop.element || {};
+
+            // use 'name' property in element or config
+            // then look for 'name' at object root
+            // lastly use the object's own name
+            prop.name = prop.name || item;
+
+            // accept 'kind' or 'type' property name
+            // but 'kind' will take priority
+            // with a fallback to a generic div
+            kind = prop.kind || prop.type || 'div.spawned';
+
+            // do a raw spawn() if 'kind' is 'element'
+            // or if there's a tag property
+            if (kind === 'element' || prop.tag) {
+                try {
+                    spawnedElement =
+                        spawn(prop.tag || prop.config.tag || 'div', prop.config);
+                    // jQuery's .append() method is
+                    // MUCH more robust and forgiving
+                    // than element.appendChild()
+                    $frag.append(spawnedElement);
+                    spawner.spawnedElements.push(spawnedElement);
+                }
+                catch (e) {
+                    if (hasConsole) console.log(e);
+                    spawner.notSpawned.push(prop);
+                }
+            }
+            else {
+                // check for a matching XNAT.ui method to call:
+                method =
+                    // XNAT.ui.kind.init()
+                    eval(NAMESPACE + '.' + kind + '.init') ||
+
+                    // XNAT.ui.kind()
+                    eval(NAMESPACE + '.' + kind) ||
+
+                    // XNAT.kind.init()
+                    eval('XNAT.' + kind + '.init') ||
+
+                    // XNAT.kind()
+                    eval('XNAT.' + kind) ||
+
+                    // kind.init()
+                    eval(kind + '.init') ||
+
+                    // kind()
+                    eval(kind);
+
+
+                // only spawn elements with defined methods
+                if (isFunction(method)) {
+
+                    // 'spawnedElement' item will be an
+                    // object with a .get() method that
+                    // will retrieve the spawned item
+                    spawnedElement = method(prop);
+
+                    // add spawnedElement to the master frag
+                    $frag.append(spawnedElement.element);
+
+                    // save a reference to spawnedElement
+                    spawner.spawnedElements.push(spawnedElement.element);
+
+                }
+                else {
+                    spawner.notSpawned.push(item);
+                }
+            }
+
+            // spawn child elements from...
+            // 'contents' or 'content' or 'children' or
+            // a property matching the value of either 'contains' or 'kind'
+            if (prop.contains || prop.contents || prop.content || prop.children || prop[prop.kind]) {
+                prop.contents = prop[prop.contains] || prop.contents || prop.content || prop.children || prop[prop.kind];
+                // if there's a 'target' property, put contents in there
+                if (spawnedElement.target) {
+                    $spawnedElement = $(spawnedElement.target);
+                }
+                else {
+                    $spawnedElement = $(spawnedElement.element);
+                }
+                $spawnedElement.append(_spawn(prop.contents).get());
+            }
+            
+            if (prop.after) {
+                $frag.append(prop.after)
+            }
+            
+            if (prop.before) {
+                $frag.prepend(prop.before)
+            }
+            
+            // if there's a .load() method, fire that
+            if (isFunction(spawnedElement.load)) {
+                spawnedElement.load();
+            }
+
+        });
+
+        _spawn.spawned = frag;
+        
+        _spawn.element = frag;
+
+        _spawn.children = frag.children;
+
+        _spawn.get = function(){
+            return frag;
+        };
+        
+        _spawn.getContents = function(){
+            return $frag.contents();    
+        };
+
+        _spawn.render = function(element, empty){
+            var $el = $$(element);
+            // empty the container element before spawning?
+            if (empty) {
+                $el.empty();
+            }
+            $el.append(frag);
+            return spawn;
+        };
+
+        _spawn.foo = '(spawn.foo)';
+
+        return _spawn;
+
+    };
+    // ==================================================
+
+
+    spawner.testSpawn = function(){
+        var jsonUrl =
+                XNAT.url.rootUrl('/page/admin/data/config/site-admin-sample-new.json');
+        return $.getJSON({
+            url: jsonUrl,
+            success: function(data){
+                spawner.spawn(data);
+            }
+        });
+    };
+
+
+    // this script has loaded
+    spawner.loaded = true;
+
+    return XNAT.spawner = spawner;
+
+}));
diff --git a/src/main/webapp/scripts/xnat/ui/input.js b/src/main/webapp/scripts/xnat/ui/input.js
new file mode 100644
index 0000000000000000000000000000000000000000..737e31ad144827582630db4b56e70988b03fd4a0
--- /dev/null
+++ b/src/main/webapp/scripts/xnat/ui/input.js
@@ -0,0 +1,158 @@
+/*!
+ * Spawn form input elements
+ */
+
+var XNAT = getObject(XNAT);
+
+(function(factory){
+    if (typeof define === 'function' && define.amd) {
+        define(factory);
+    }
+    else if (typeof exports === 'object') {
+        module.exports = factory();
+    }
+    else {
+        return factory();
+    }
+}(function(){
+
+    var undefined, textTypes,
+        numberTypes, otherTypes,
+        $ = jQuery || null, // check and localize
+        input_, input;
+
+    XNAT.ui = getObject(XNAT.ui || {});
+
+    // if XNAT.ui.input is already defined,
+    // save it and its properties to add later
+    // as methods and properties to the function
+    input_ = XNAT.ui.input || {};
+
+    function lookupValue(el, lookup){
+        var val = '';
+        try {
+            val = eval(lookup.trim())
+        }
+        catch (e) {
+            val = '';
+            console.log(e);
+        }
+        el.value = val;
+        return val;
+    }
+
+    function lookupObjectValue(root, objStr){
+        var val = '';
+        if (!objStr) {
+            objStr = root;
+            root = window;
+        }
+        root = root || window;
+        objStr.toString().trim().split('.').forEach(function(part, i){
+            part = part.trim();
+            // start at the root object
+            if (i === 0) {
+                val = root[part] || {};
+            }
+            else {
+                val = val[part];
+            }
+        });
+        return val;
+    }
+
+
+    // ========================================
+    // MAIN FUNCTION
+    input = function(type, config){
+        // only one argument?
+        // could be a config object
+        if (!config && typeof type != 'string') {
+            config = type;
+            type = null; // it MUST contain a 'type' property
+        }
+        config = getObject(config);
+        config.type = type || config.type || 'text';
+        // lookup a value if it starts with '??'
+        var doLookup = '??';
+        if (config.value && config.value.toString().indexOf(doLookup) === 0) {
+            config.value = lookupObjectValue(config.value.split(doLookup)[1])
+        }
+        // lookup a value from a namespaced object
+        // if no value is given
+        if (!config.value && config.data && config.data.lookup) {
+            config.value = lookupObjectValue(config.data.lookup)
+        }
+        var spawned = spawn('input', config);
+        return {
+            element: spawned,
+            spawned: spawned,
+            get: function(){
+                return spawned;
+            }
+        }
+    };
+    // ========================================
+
+
+    function setupType(type, className, opts){
+        opts = getObject(opts);
+        var config = getObject(opts.config || opts.element || {});
+        config.addClass = className;
+        config.data = extend({validate: className}, config.data);
+        if (!config.data.validate) delete config.data.validate;
+        return input(type, config);
+    }
+
+    // methods for direct creation of specific input types
+    // some are 'real' element types, others are XNAT-specific
+    textTypes = [
+        'text', 'email', 'url', 'strict',
+        'id', 'alpha', 'alphanum'
+    ];
+    textTypes.forEach(function(type){
+        input[type] = function(config){
+            return setupType('text', type, config);
+        }
+    });
+
+    numberTypes = ['number', 'int', 'integer', 'float'];
+    numberTypes.forEach(function(type){
+        input[type] = function(config){
+            return setupType('number', type, config);
+        }
+    });
+
+    otherTypes = [
+        'password', 'date', 'checkbox',
+        'radio', 'button', 'hidden', 'file'
+    ];
+    otherTypes.forEach(function(type){
+        input[type] = function(config){
+            return setupType(type, type, config);
+        }
+    });
+
+    // save a list of all available input types
+    input.types = [].concat(textTypes, numberTypes, otherTypes);
+
+    // after the page is finished loading, set empty
+    // input values from [data-lookup] attribute
+    $(window).load(function(){
+        $(':input[data-lookup]').each(function(){
+            var $input = $(this);
+            var val = lookupObjectValue($input.dataAttr('lookup'));
+            $input.changeVal(val);
+        });
+    });
+
+    // add back items that may have been on
+    // a global XNAT.ui.input object or function
+    extend(input, input_);
+
+    // this script has loaded
+    input.loaded = true;
+
+    return XNAT.ui.input = input;
+
+}));
diff --git a/src/main/webapp/scripts/xnat/ui/panel.js b/src/main/webapp/scripts/xnat/ui/panel.js
index d8d66b1dde0638357b3d9f533c2f0dc62b2871f6..6753bcdaf4e9d8cc65c0bc94e5fb66d0b773a326 100644
--- a/src/main/webapp/scripts/xnat/ui/panel.js
+++ b/src/main/webapp/scripts/xnat/ui/panel.js
@@ -2,103 +2,700 @@
  * Functions for creating XNAT tab UI elements
  */
 
-var XNAT = getObject(XNAT||{});
+var XNAT = getObject(XNAT || {});
 
 (function(XNAT, $, window, undefined){
 
-    var spawn = window.spawn,
+    var panel,
+        spawn   = window.spawn,
         element = XNAT.element;
 
 
+    XNAT.ui =
+        getObject(XNAT.ui || {});
+
+    XNAT.ui.panel = panel =
+        getObject(XNAT.ui.panel || {});
+
+    // add new element class without destroying existing class
+    function addClassName(el, newClass){
+        el.className = [].concat(el.className||[], newClass).join(' ').trim();
+        return el.className;
+    }
+
+    // add new data object item to be used for [data-] attribute(s)
+    function addDataObjects(obj, attrs){
+        obj.data = obj.data || {};
+        forOwn(attrs, function(name, prop){
+            obj.data[name] = prop;
+        });
+        return obj.data;
+    }
+
+    // another way to do this without using eval()
+    // is to loop over object string using dot notation:
+    // var myVal = lookupObjectValue(XNAT, 'data.siteConfig.siteId');
+    // --> myVal == 'myXnatSiteId'
+    function lookupObjectValue(root, objStr){
+        var val = '';
+        if (!objStr) {
+            objStr = root;
+            root = window;
+        }
+        root = root || window;
+        objStr.toString().trim().split('.').forEach(function(part, i){
+            // start at the root object
+            if (i === 0) {
+                val = root[part] || {};
+            }
+            else {
+                val = val[part];
+            }
+        });
+        return val;
+    }
+
+    // string that indicates to look for a namespaced object value
+    var doLookupString = '??';
+
+    function doLookup(input){
+        if (!input) return '';
+        if (input.toString().trim().indexOf(doLookupString) === 0){
+            return lookupObjectValue(window, input.split(doLookupString)[1]);
+        }
+        return input;
+    }
+
     /**
-     * Initialize panel and optionally render it.
-     * If 'opts' argument is passed, 'setup' method will run.
-     * If 'container' argument is passed, the 'render' method will run.
-     * So if both args are passed, 'setup' and 'render' do NOT need to be called.
+     * Initialize panel.
      * @param [opts] {Object} Config object
-     * @param [container] {Element} Container for panel
      * @returns {{}}
      */
-    function panel(opts, container){
+    panel.init = function panelInit(opts){
+
+        opts = cloneObject(opts);
+        opts.element = opts.element || opts.config || {};
 
-        // `this` object
-        var __ = {};
+        var _target = spawn('div.panel-body', opts.element),
 
-        /**
-         * Standard panel widget
-         */
-        function newPanel(){
+            hideFooter = (isDefined(opts.footer) && (opts.footer === false || /^-/.test(opts.footer))),
 
-            var sections = [
+            _panel  = spawn('div.panel.panel-default', [
                 ['div.panel-heading', [
-                    ['h3.panel-title', __.opts.title]
+                    ['h3.panel-title', opts.title || opts.label]
                 ]],
-                ['div.panel-body', __.opts.body]
-            ];
 
-            if (__.opts.footer){
-                sections.push(['div.panel-footer', __.opts.footer])
-            }
+                // target is where the next spawned item will render
+                _target,
+
+                (hideFooter ? ['div.hidden'] : ['div.panel-footer', opts.footer])
+
+            ]);
 
-            return spawn((__.opts.tag) + '.panel.panel-default', __.opts.attr, sections);
-            //return $(spawn('div.panel.panel-default')).append(content);
+        // add an id to the outer panel element if present
+        if (opts.id || opts.element.id) {
+            _panel.id = (opts.id || opts.element.id) + '-panel';
+        }
+
+        return {
+            target: _target,
+            element: _panel,
+            spawned: _panel,
+            get: function(){
+                return _panel;
+            }
         }
+    };
+
+    // creates a panel that's a form that can be submitted
+    panel.form = function panelForm(opts){
+
+        opts = cloneObject(opts);
+        opts.element = opts.element || opts.config || {};
+
+        var _target = spawn('div.panel-body', opts.element),
 
-        /**
-         * Sets up elements before rendering to the page
-         * @param _opts Config object
-         * @returns {{}}
-         */
-        __.setup = function(_opts){
+            hideFooter = (isDefined(opts.footer) && (opts.footer === false || /^-/.test(opts.footer))),
 
-            __.opts = extend(true, {}, _opts);
+            _resetBtn = spawn('button.btn.btn-sm.btn-default.revert.pull-right|type=button', 'Discard Changes'),
 
-            __.opts.tag    = __.opts.tag    || 'div';
-            __.opts.title  = __.opts.title  || __.opts.header  || '';
-            __.opts.body   = __.opts.body   || __.opts.content || '';
-            __.opts.footer = __.opts.footer || '';
+            _footer = [
+                ['button.btn.btn-sm.btn-primary.save.pull-right|type=submit', 'Submit'],
+                ['span.pull-right', '&nbsp;&nbsp;&nbsp;'],
+                _resetBtn,
+                ['button.btn.btn-sm.btn-link.defaults.pull-left', 'Default Settings'],
+                ['div.clear']
+            ],
+
+            _formPanel = spawn('form.xnat-form-panel.panel.panel-default', {
+                method: opts.method || 'POST',
+                action: XNAT.url.rootUrl(opts.action || '') || '#'
+            }, [
+                ['div.panel-heading', [
+                    ['h3.panel-title', opts.title || opts.label]
+                ]],
 
-            __.opts.attr = __.opts.attr || {};
+                // target is where the next spawned item will render
+                _target,
 
-            if (__.opts.id){
-                __.opts.attr.id = __.opts.id
+                (hideFooter ? ['div.hidden'] : ['div.panel-footer', opts.footer || _footer])
+
+            ]);
+
+        // add an id to the outer panel element if present
+        if (opts.id || opts.element.id) {
+            _formPanel.id = (opts.id || opts.element.id) + '-panel';
+        }
+
+        // set form element values from an object map
+        function setValues(form, dataObj){
+            // pass a single argument to work with this form
+            if (!dataObj) {
+                dataObj = form;
+                form = _formPanel;
             }
+            // find all form inputs with a name attribute
+            $$(form).find(':input[name]').each(function(){
+                var val = '';
+                if (Array.isArray(dataObj)) {
+                    val = dataObj.join(', ');
+                }
+                else {
+                    val = /string|number/i.test(typeof dataObj) ? dataObj : dataObj[this.name] || '';
+                }
+                $(this).changeVal(val);
+            });
+            if (xmodal && xmodal.loading && xmodal.loading.close){
+                xmodal.loading.close();
+            }
+        }
 
-            if (__.opts.name){
-                __.opts.attr.data = getObject(__.opts.attr.data);
-                __.opts.attr.data.name = __.opts.name;
+        // populate the data fields if this panel is in the 'active' tab
+        // (only getting values for the active tab should cut down on requests)
+        function loadData(obj){
+
+            if (!obj) {
+                obj = opts.load || {};
             }
 
-            __.panel = __.element = newPanel();
+            obj = cloneObject(obj);
 
-            return __;
-        };
+            obj.form = obj.form || obj.target || obj.element || _formPanel;
+
+            // need a form to put the data into
+            if (!obj.form) return;
+
+            // // if there's a 'refresh' url, make that obj.url
+            // if (obj.refresh) obj.url = obj.refresh;
+
+            // if we pass data in a 'lookup' property, just use that
+            // to avoid doing a server request
+
+            if (obj.lookup && !obj.url) {
+                if (Array.isArray(obj.lookup)) {
+                    obj.lookup = obj.lookup[0];
+                }
+                else {
+                    try {
+                        obj.lookup = eval(obj.lookup);
+                    }
+                    catch (e) {
+                        if (console && console.log) console.log(e);
+                        obj.lookup = ''
+                    }
+                }
+                setValues(obj.form, obj.lookup);
+                return obj.form;
+            }
+
+            // otherwise try to get the data values via ajax
+
+            // need a url to get the data
+            if (!obj.url) return obj.form;
+
+            obj.method = obj.method || 'GET';
+
+            // setup the ajax request
+            // override values with an
+            // 'ajax' or 'xhr' property
+            obj.ajax = extend(true, {
+                method: obj.method,
+                url: XNAT.url.restUrl(obj.url)
+            }, obj.ajax || obj.xhr);
+
+            // allow use of 'prop' or 'root' for the root property name
+            obj.prop = obj.prop || obj.root;
+
+            obj.ajax.success = function(data){
+                var prop = data;
+                // if there's a property to target,
+                // specify the 'prop' property
+                if (obj.prop){
+                    obj.prop.split('.').forEach(function(part){
+                        prop = prop[part];
+                    });
+                }
+                setValues(prop);
+            };
+
+            // return the ajax thing for method chaining
+            return XNAT.xhr.request(obj.ajax);
 
-        // if 'opts' arg is passed to .panel(), call .setup()
-        if (opts){
-            __.setup(opts);
         }
 
-        // render the panel and append to 'container'
-        __.render = function(container){
-            $$(container).append(__.panel);
-            return __;
+        //if (opts.load){
+        //    loadData(opts.load);
+        //}
+
+        $(_formPanel).on('reload-data', function(){
+            xmodal.loading.open();
+            opts.load.url = opts.load.url || opts.load.refresh;
+            loadData(opts.load);
+        });
+
+        // click 'Discard Changes' button to reload data
+        _resetBtn.onclick = function(){
+            $(_formPanel).triggerHandler('reload-data');
         };
 
-        // render immediately if 'container' is specified
-        if (container){
-            __.render(container);
+        // intercept the form submit to do it via REST instead
+        $(_formPanel).on('submit', function(e){
+
+            e.preventDefault();
+
+            xmodal.loading.open();
+
+            var ajaxSubmitOpts = {
+
+                target:        '#server-response',  // target element(s) to be updated with server response
+                beforeSubmit:  function(){},  // pre-submit callback
+                success:       function(){},  // post-submit callback
+
+                // other available options:
+                url:       '/url/for/submit', // override for form's 'action' attribute
+                type:      'get or post (or put?)', // 'get' or 'post', override for form's 'method' attribute
+                dataType:  null,        // 'xml', 'script', or 'json' (expected server response type)
+                clearForm: true,        // clear all form fields after successful submit
+                resetForm: true,        // reset the form after successful submit
+
+                // $.ajax options can be used here too, for example:
+                timeout:   3000
+
+            };
+
+            function formToJSON(form){
+                var json = {};
+                $$(form).serializeArray().forEach(function(item) {
+                    if (typeof json[item.name] == 'undefined') {
+                        json[item.name] = item.value || '';
+                    }
+                    else {
+                        json[item.name] = [].concat(json[item.name], item.value||[]) ;
+                    }
+                });
+                return json;
+            }
+
+            var ajaxConfig = {
+                method: opts.method,
+                url: this.action,
+                success: function(data){
+                    var obj = {};
+                    // if a data object is returned,
+                    // just use that
+                    if (data) {
+                        // wrap the returned data in an array so the
+                        // loadData() function handles it properly
+                        obj.lookup = [data];
+                    }
+                    else {
+                        obj.url = opts.refresh;
+                    }
+                    xmodal.loading.close();
+                    xmodal.message('Data saved successfully.', {
+                        action: function(){
+                            xmodal.closeAll();
+                            loadData(obj);
+                        }
+                    });
+                }
+            };
+
+            // if (!/form/i.test(opts.contentType||'')) {
+            //     ajaxConfig.contentType = opts.contentType;
+            // }
+
+            if (/json/i.test(opts.contentType||'')){
+                ajaxConfig.data = JSON.stringify(formToJSON(this));
+                ajaxConfig.processData = false;
+                ajaxConfig.contentType = 'application/json';
+                $.ajax(ajaxConfig);
+            }
+            else {
+                // ajaxConfig.data =  $(this).serialize();
+                // $.ajax(ajaxConfig);
+                $(this).ajaxSubmit(ajaxConfig);
+            }
+
+            // $.ajax(ajaxConfig);
+
+            return false;
+
+        });
+
+        // this object is returned to the XNAT.spawner() method
+        return {
+            load: loadData,
+            setValues: setValues,
+            target: _target,
+            element: _formPanel,
+            spawned: _formPanel,
+            get: function(){
+                return _formPanel;
+            }
         }
+    };
+    
+    // creates a panel that submits all forms contained within
+    panel.multiForm = function(opts){
+
+        opts = cloneObject(opts);
+        opts.element = opts.element || opts.config || {};
+
+        var inner = spawn('div.panel-body', opts.element),
+
+            hideFooter = (isDefined(opts.footer) && (opts.footer === false || /^-/.test(opts.footer))),
+
+            submitBtn = spawn('button', {
+                type: 'submit',
+                classes: 'btn btn-sm btn-primary save pull-right',
+                html: 'Save All'
+            }),
+            
+            resetBtn  = spawn('button', {
+                type: 'button',
+                classes: 'btn btn-sm btn-default revert pull-right',
+                html: 'Discard Changes',
+                onclick: function(e){
+                    e.preventDefault();
+                    $(this).closest('form.multi-form').find('form').each(function(){
+                        $(this).triggerHandler('reload-data');
+                    });
+                    return false;
+                }
+            }),
+            
+            defaults = spawn('button', {
+                type: 'button',
+                classes: 'btn btn-sm btn-link defaults pull-left',
+                html: 'Default Settings'
+            }),
+
+            footer = [
+                submitBtn,
+                ['span.pull-right', '&nbsp;&nbsp;&nbsp;'],
+                resetBtn,
+                // defaults,
+                ['div.clear']
+            ],
+
+            multiForm = spawn('form', {
+                classes: 'xnat-form-panel multi-form panel panel-default', 
+                method: opts.method || 'POST',
+                action: opts.action || '#',
+                onsubmit: function(e){
+                    e.preventDefault();
+                    // submit all enclosed forms
+                    $(this).find('form').each(function(){
+                        xmodal.closeAll();
+                        $(this).trigger('submit');
+                    });
+                    return false;
+                }
+            }, [
+                ['div.panel-heading', [
+                    ['h3.panel-title', opts.title || opts.label]
+                ]],
 
-        __.get = function(){
-            return __.element;
-        };
+                
+                // 'inner' is where the next spawned item will render
+                inner,
+
+                
+                (hideFooter ? ['div.hidden'] : ['div.panel-footer', opts.footer || footer])
+
+            ]);
+
+        // add an id to the outer panel element if present
+        if (opts.id || opts.element.id) {
+            multiForm.id = opts.id || (opts.element.id + '-panel');
+        }
+        
+        return {
+            target: inner,
+            element: multiForm,
+            spawned: multiForm,
+            get: function(){
+                return multiForm
+            }
+        }
+    };
+
+    // create a single generic panel element
+    panel.element = function(opts){
+
+        var _element, _inner = [], _target;
+        opts = cloneObject(opts);
+        opts.element = opts.element || opts.config || {};
+        if (opts.id || opts.element.id) {
+            opts.element.id = (opts.id || opts.element.id) + '-element';
+        }
+        addClassName(opts.element, 'panel-element');
+        addDataObjects(opts.element, { name: opts.name||'' });
+        opts.label = opts.label||opts.title||opts.name||'';
+
+        _inner.push(['div.element-label', opts.label]);
+
+        // 'contents' will be inserted into the 'target' element
+        _target = spawn('div.element-wrapper');
+
+        // add the target to the content array
+        _inner.push(_target);
+
+        // add a description if there is one
+        if (opts.description){
+            _inner.push(['div.description', opts.description||opts.body||opts.html]);
+        }
+
+        _element = spawn('div', opts.element, _inner);
+
+        return {
+            target: _target,
+            element: _element,
+            spawned: _element,
+            get: function(){
+                return _element
+            }
+        }
+
+    };
 
-        return __;
+    panel.subhead = function(opts){
+        opts = cloneObject(opts);
+        opts.html = opts.html || opts.text || opts.label;
+        return XNAT.ui.template.panelSubhead(opts).spawned;
+    };
+
+    // return a generic panel 'section'
+    panel.section = function(opts){
+
+        var _section, _inner = [], _body;
+
+        opts = cloneObject(opts);
+        opts.element = opts.element || opts.config || {};
+        opts.header = opts.header || opts.label || opts.title || '';
+
+        if (opts.header) {
+            _inner.push(['header.section-header', opts.header]);
+        }
+
+        // this needs to be spawned here to act as
+        // the target for this elements 'contents'
+        _body = spawn('div.section-body');
 
+        _inner.push(_body);
+
+        if (opts.footer) {
+            _inner.push(['footer.section-footer'], opts.footer);
+        }
+
+        _section = spawn('div.panel-section', opts.element, _inner);
+
+        return {
+            target: _body,
+            element: _section,
+            spawned: _section,
+            get: function(){
+                return _section;
+            }
+        }
+
+    };
+
+    panel.input = {};
+
+    panel.display = function(opts){
+        return XNAT.ui.template.panelDisplay(opts).spawned;
+    };
+    
+    panel.input.text = function(opts){
+        return XNAT.ui.template.panelInput(opts).spawned;
+    };
+
+    panel.input.number = function panelInputNumber(opts){
+        opts = cloneObject(opts);
+        opts.type = 'number';
+        return XNAT.ui.template.panelInput(opts).spawned;
+    };
+
+    panel.input.email = function panelInputEmail(opts){
+        opts = cloneObject(opts);
+        opts.type = 'text';
+        addClassName(opts, 'email');
+        return XNAT.ui.template.panelInput(opts).spawned;
+    };
+
+    panel.input.password = function panelInputPassword(opts){
+        opts = cloneObject(opts);
+        opts.type = 'password';
+        addClassName(opts, 'password');
+        return XNAT.ui.template.panelInput(opts).spawned;
+    };
+
+    panel.input.checkbox = function panelInputCheckbox(opts){
+        opts = cloneObject(opts);
+        opts.type = 'checkbox';
+        //addClassName(opts, 'checkbox');
+        return XNAT.ui.template.panelInput(opts).spawned;
+    };
+
+    panel.input.upload = function panelInputUpload(opts){
+        opts = cloneObject(opts);
+        opts.id = (opts.id||randomID('upload-', false));
+        opts.element = opts.element || opts.config || {};
+        opts.element.id = opts.id;
+        var form = ['form', {
+            id: opts.id + '-form',
+            method: opts.method || 'POST',
+            action: opts.action || '#',
+            className: addClassName(opts, 'file-upload')
+        }, [
+            ['input', {
+                type: 'file',
+                id: opts.id + '-input',
+                multiple: true,
+                className: addClassName(opts, 'file-upload-input')
+            }],
+            ['button', {
+                type: 'submit',
+                id: opts.id +'-button',
+                html: 'Upload'
+            }]
+        ]];
+        return XNAT.ui.template.panelInput(opts, form).spawned;
+    };
+
+    panel.input.group = function panelInputGroup(obj){
+        var _inner = spawn('div.element-group');
+        var _outer = XNAT.ui.template.panelElementGroup(obj, [_inner]).spawned;
+        return {
+            target: _inner,
+            element: _outer,
+            spawned: _outer,
+            get: function(){
+                return _outer;
+            }
+        }
+    };
+
+    panel.textarea = function(opts){
+        opts = cloneObject(opts);
+        opts.element = opts.element || opts.config || {};
+        if (opts.id) opts.element.id = opts.id;
+        if (opts.name) opts.element.name = opts.name;
+        opts.element.html =
+            opts.element.html ||
+            opts.element.value ||
+            opts.value ||
+            opts.text ||
+            opts.html || '';
+
+        opts.element.html = doLookup(opts.element.html);
+
+        var textarea = spawn('textarea', opts.element);
+        return XNAT.ui.template.panelDisplay(opts, textarea).spawned;
+    };
+
+    panel.select = {};
+
+    panel.select.menu = function panelSelectSingle(opts, multi){
+
+        var _menu;
+
+        opts = cloneObject(opts);
+        opts.element = opts.element || opts.config || {};
+        opts.element.name = opts.element.name || opts.name || '';
+        opts.element.id = opts.element.id || opts.id || toDashed(opts.element.name);
+        if (multi) {
+            opts.element.multiple = true;
+        }
+        _menu = spawn('select', opts.element, [['option|value=!', 'Select']]);
+
+        if (opts.options){
+            forOwn(opts.options, function(name, prop){
+                _menu.appendChild(spawn('option', {
+                    value: prop.value,
+                    selected: prop.selected || (prop.value === opts.value)
+                }, prop.label));
+            });
+        }
+        return XNAT.ui.template.panelInput({
+            label: opts.label,
+            name: opts.name
+        }, _menu).spawned;
+    };
+    panel.select.init = panel.select.menu;
+    panel.select.single = panel.select.menu;
+
+    panel.select.multi = function panelSelectMulti(opts){
+        return panel.select.menu(opts, true)
+    };
+
+    panel.selectMenu = function panelSelectMenu(opts){
+        opts = cloneObject(opts);
+        return XNAT.ui.template.panelSelect(opts).spawned;
+    };
+
+
+    function footerButton(text, type, disabled, classes){
+        var button = {
+            type: type || 'button',
+            html: text || 'Submit'
+        };
+        button.classes = [classes || '', 'btn btn-sm'];
+        if (type === 'link') {
+            button.classes.push('btn-link')
+        }
+        else if (/submit|primary/.test(type)) {
+            button.classes.push('btn-primary')
+        }
+        else {
+            button.classes.push('btn-default')
+        }
+        if (disabled) {
+            button.classes.push('disabled');
+            button.disabled = 'disabled'
+        }
+        return spawn('button', button);
     }
 
 
+    return XNAT.ui.panel = panel;
+
+
+    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+    // STOP EVERYTHING!!!!!
+    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+
+    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+    // EVERYTHING BELOW HERE IS EFFECTIVELY DISABLED
+    // WITH THE return STATEMENT ABOVE
+    //
+    // IT IS BEING KEPT AROUND TEMPORARILY FOR REFERENCE
+    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+
 
     /**
      * Panel widget with default 'Submit' and 'Revert' buttons
@@ -106,10 +703,9 @@ var XNAT = getObject(XNAT||{});
      * @param container
      * @returns {*}
      */
-    panel.form = function(opts, container){
+    panel.form = function panelForm(opts, container){
 
-        var __ = {},
-            _panel, $panel,
+        var _panel, $panel,
             saveBtn, revertBtn,
             $saveBtn, $revertBtn;
 
@@ -119,15 +715,15 @@ var XNAT = getObject(XNAT||{});
 
         opts.body = [];
 
-        if (opts.description){
-            opts.body.push(element.p(opts.description||''))
+        if (opts.description) {
+            opts.body.push(element.p(opts.description || ''))
         }
 
-        if (opts.elements){
+        if (opts.elements) {
             opts.body = opts.body.concat(setupElements(opts.elements))
         }
 
-        saveBtn   = footerButton('Save', 'submit', true, 'save pull-right');
+        saveBtn = footerButton('Save', 'submit', true, 'save pull-right');
         revertBtn = footerButton('Discard Changes', 'button', true, 'revert pull-right');
 
         opts.footer = [
@@ -152,7 +748,7 @@ var XNAT = getObject(XNAT||{});
 
         var url = '#';
 
-        if (method === 'GET'){
+        if (method === 'GET') {
             url = XNAT.url.restUrl(opts.url)
         }
         else if (/PUT|POST|DELETE/.test(method)) {
@@ -187,7 +783,7 @@ var XNAT = getObject(XNAT||{});
                         }
                     })
                 }
-                catch(e) {
+                catch (e) {
                     setDisabled([$saveBtn, $revertBtn], true);
                     console.log(e)
                 }
@@ -203,20 +799,26 @@ var XNAT = getObject(XNAT||{});
 
         _panel.panel = _panel.element;
 
-        return _panel
+        return {
+            element: _panel,
+            spawned: _panel,
+            get: function(){
+                return _panel;
+            }
+        }
 
     };
 
     function footerButton(text, type, disabled, classes){
         var button = {
-            type: type||'button',
-            html: text||'Submit'
+            type: type || 'button',
+            html: text || 'Submit'
         };
-        button.classes = [classes||'', 'btn btn-sm'];
-        if (type === 'link'){
+        button.classes = [classes || '', 'btn btn-sm'];
+        if (type === 'link') {
             button.classes.push('btn-link')
         }
-        else if (/submit|primary/.test(type)){
+        else if (/submit|primary/.test(type)) {
             button.classes.push('btn-primary')
         }
         else {
@@ -246,23 +848,23 @@ var XNAT = getObject(XNAT||{});
 
         radios = item.options.map(function(radio){
 
-            var label = {},
-                button = spawn('input', {
+            var label       = {},
+                button      = spawn('input', {
                     type: 'radio',
                     name: item.name,
                     value: radio.value
                 }),
                 description = spawn('div.description', {
-                    data: { 'for': item.value },
+                    data: {'for': item.value},
                     title: item.value,
                     html: radio.description
                 });
 
-            if (button.value === item.value){
+            if (button.value === item.value) {
                 button.checked = true;
             }
 
-            if (!button.checked){
+            if (!button.checked) {
                 $(description).addClass('hidden');
                 //button.disabled = true;
                 //label.classes = 'hidden';
@@ -277,7 +879,7 @@ var XNAT = getObject(XNAT||{});
 
             label.append = description;
 
-            return ['label.radio-item', label, [button, ' '+radio.label]];
+            return ['label.radio-item', label, [button, ' ' + radio.label]];
 
         });
 
@@ -291,91 +893,91 @@ var XNAT = getObject(XNAT||{});
         var elements = [],
             element, tag,
             children = '',
-            before = [],
-            after = [],
-            kind = item.kind || '',
-            obj = {};
+            before   = [],
+            after    = [],
+            kind     = item.kind || '',
+            obj      = {};
 
         // input (or other) element
-        tag = item.tag||item.kind||'div';
+        tag = item.tag || item.kind || 'div';
 
-        if (kind === 'element-group' && item.elements.length){
+        if (kind === 'element-group' && item.elements.length) {
             element = groupElements(item.elements);
             //element = spawn(tag, [radioToggle(item)]);
         }
         else {
-            if (item.name){
+            if (item.name) {
                 obj.name = item.name
             }
 
-            if (item.type){
+            if (item.type) {
                 obj.type = item.type;
             }
         }
 
-        if (item.id){
+        if (item.id) {
             obj.id = item.id;
         }
 
-        if (tag === 'input' && !item.type){
+        if (tag === 'input' && !item.type) {
             obj.type = 'text';
         }
 
         // 'checkbox' kind
-        if (kind === 'checkbox'){
+        if (kind === 'checkbox') {
             tag = 'input';
             obj.type = 'checkbox';
         }
 
         // set a default 'size' value for text inputs
-        if (tag === 'input' && /text|email|url/.test(item.type||obj.type||'')){
+        if (tag === 'input' && /text|email|url/.test(item.type || obj.type || '')) {
             obj.size = '25';
         }
 
-        if (item.label){
+        if (item.label) {
             obj.title = item.label;
         }
 
         obj.data = item.data ? extend(true, {}, item.data) : {};
 
-        if (item.value){
+        if (item.value) {
             obj.value = item.value;
             obj.data.value = item.value;
         }
 
-        if (item.checked){
+        if (item.checked) {
             obj.checked = true;
             obj.data.state = 'checked';
         }
 
-        if (item.info){
-            obj.data.info =  item.info;
+        if (item.info) {
+            obj.data.info = item.info;
         }
 
-        if (item.attr || item.attributes){
+        if (item.attr || item.attributes) {
             obj.attr = item.attr || item.attributes || {};
         }
 
-        if (/form-table|inputTable|input-table/i.test(kind)){
+        if (/form-table|inputTable|input-table/i.test(kind)) {
             element = XNAT.ui.inputTable(item.tableData).get();
         }
         else {
             obj.innerHTML = [].concat(item.innerHTML || item.html || []).join('\n');
         }
 
-        if (item.before){
+        if (item.before) {
             console.log('before');
             before = item.before;
             //elements.push(spawn('span.before', item.before))
         }
 
-        if (item.after){
+        if (item.after) {
             console.log('after');
             after = item.after;
             //elements.push(spawn('span.after', item.after))
         }
 
-        if (kind !== 'hidden'){
+        if (kind !== 'hidden') {
             // enable the 'Save' and 'Discard Changes' buttons on change
             obj.onchange = function(){
                 var $panel = $(this).closest('.panel');
@@ -383,13 +985,13 @@ var XNAT = getObject(XNAT||{});
             };
         }
 
-        if (kind === 'select' && item.options){
+        if (kind === 'select' && item.options) {
             children = item.options.map(function(option){
                 var obj = {};
                 obj.value = option.value;
                 obj.html = option.label;
-                if (isDefined(item.value)){
-                    if (item.value === obj.value){
+                if (isDefined(item.value)) {
+                    if (item.value === obj.value) {
                         obj.selected = true;
                     }
                 }
@@ -399,11 +1001,11 @@ var XNAT = getObject(XNAT||{});
 
         element = element || spawn(tag, obj, children);
 
-        if (!elements.length){
+        if (!elements.length) {
             elements = [].concat(before, element, after);
         }
         // add a description if present
-        if (item.description){
+        if (item.description) {
             elements.push(spawn('div.description', item.description))
         }
 
@@ -414,9 +1016,9 @@ var XNAT = getObject(XNAT||{});
 
     function elementLabel(label, id){
         var obj = {
-            innerHTML: label||''
+            innerHTML: label || ''
         };
-        if (id){
+        if (id) {
             obj.attr = {
                 'for': id
             }
@@ -430,7 +1032,7 @@ var XNAT = getObject(XNAT||{});
         return items.map(function(item){
             var label = '';
             var tag = item.kind === 'hidden' ? 'div.hidden' : 'div.group-item';
-            if (item.label){
+            if (item.label) {
                 label = elementLabel(item.label, item.id)
             }
             tag += '|data-name=' + item.name;
@@ -452,7 +1054,7 @@ var XNAT = getObject(XNAT||{});
                     tag += '.element-group';
                     break;
             }
-            if (item.label){
+            if (item.label) {
                 label = elementLabel(item.label, item.id)
             }
             tag += '|data-name=' + item.name;
@@ -467,9 +1069,9 @@ var XNAT = getObject(XNAT||{});
         // reset all checkboxes and radio buttons
         $form.find(':checkbox, :radio').each(function(){
             var $this = $(this);
-            if ($this.hasClass('dirty')){
-                if ($this.data('state') !== 'checked'){
-                    if ($this.is(':checked')){
+            if ($this.hasClass('dirty')) {
+                if ($this.data('state') !== 'checked') {
+                    if ($this.is(':checked')) {
                         $this.trigger('click')
                     }
                     else {
@@ -501,12 +1103,12 @@ var XNAT = getObject(XNAT||{});
 
         var sourceVal = $source.val();
         var targetVal = $target.val();
-        var dataVal   = $target.data('value');
+        var dataVal = $target.data('value');
 
         $target[0].value = (targetVal === sourceVal) ? dataVal : sourceVal;
 
         // avoid infinite loop of change triggers
-        if ($target[0] !== $$(modifier)[0]){
+        if ($target[0] !== $$(modifier)[0]) {
             $target.trigger('change.modify');
         }
 
@@ -525,7 +1127,7 @@ var XNAT = getObject(XNAT||{});
     function setHidden(elements, hidden){
         [].concat(elements).forEach(function(element){
             var showOrHide, modifyClass;
-            if (!!hidden){
+            if (!!hidden) {
                 showOrHide = 'hide';
                 modifyClass = 'addClass';
             }
@@ -539,7 +1141,7 @@ var XNAT = getObject(XNAT||{});
 
 
     XNAT.ui = getObject(XNAT.ui || {});
-    XNAT.ui.panel = panel.form; // temporarily use the 'form' panel kind
+    XNAT.ui.panel = panel; // temporarily use the 'form' panel kind
     XNAT.ui.panelForm = XNAT.ui.formPanel = panel.form;
 
 
@@ -566,9 +1168,9 @@ var XNAT = getObject(XNAT||{});
                 var args = parts[1].split(',');
 
                 var _target = args[0].trim();
-                var _source = (args[1]||'').trim() || _target;
+                var _source = (args[1] || '').trim() || _target;
 
-                if (args[1]){
+                if (args[1]) {
                     _source = args[1].trim();
                 }
 
@@ -630,5 +1232,4 @@ var XNAT = getObject(XNAT||{});
     });
 
 
-
-})(XNAT, jQuery, window);
\ No newline at end of file
+})(XNAT, jQuery, window);
diff --git a/src/main/webapp/scripts/xnat/ui/select.js b/src/main/webapp/scripts/xnat/ui/select.js
new file mode 100644
index 0000000000000000000000000000000000000000..d3c136e49f580ace2ca9b557d33e852d26da3a86
--- /dev/null
+++ b/src/main/webapp/scripts/xnat/ui/select.js
@@ -0,0 +1,107 @@
+/*!
+ * Spawn form input elements
+ */
+
+var XNAT = getObject(XNAT);
+
+(function(factory){
+    if (typeof define === 'function' && define.amd) {
+        define(factory);
+    }
+    else if (typeof exports === 'object') {
+        module.exports = factory();
+    }
+    else {
+        return factory();
+    }
+}(function(){
+
+    var undefined, select;
+
+    XNAT.ui = getObject(XNAT.ui || {});
+
+    // if XNAT.ui.input is already defined,
+    // save it and its properties to add later
+    // as methods and properties to the function
+    select = getObject(XNAT.ui.select || {});
+
+    // ========================================
+    // MAIN FUNCTION
+    select.menu = function(config){
+
+        var frag = document.createDocumentFragment(),
+            menu, label;
+
+        config = getObject(config);
+
+        // show the label on the left by default
+        config.layout = config.layout || 'left';
+
+        config.id = config.id || randomID('menu-', false);
+
+        config.element = extend(true, {
+            id: config.id,
+            name: config.name || config.id
+        }, config.element);
+
+        menu = spawn('select', config.element);
+        //menu = XNAT.element().select(config.element);
+
+        menu.appendChild(spawn('option|value=!', 'Select'));
+
+        if (config.options){
+            forOwn(config.options, function(name, opt){
+                opt.value = opt.value || name;
+                opt.html  = opt.html  || opt.value;
+                menu.appendChild(spawn('option', opt));
+            });
+        }
+        
+        // if there's no label, wrap the 
+        // <select> inside a <label> element
+        if (!config.label) {
+            //frag = XNAT.element().label(menu.get());
+            frag = spawn('label', [menu]);
+        } 
+        else {
+            label = spawn('label', {
+                attr: { for: config.id }
+            }, config.label);
+            
+            if (config.layout !== 'right') {
+                frag.appendChild(label);
+            }
+
+            frag.appendChild(menu.get());
+
+            // seems redundant, but... it's not!
+            if (config.layout === 'right') {
+                frag.appendChild(label);
+            }
+        }
+
+        return {
+            element: frag,
+            spawned: frag,
+            get: function(){
+                return frag;
+            }
+        }
+    };
+    // ========================================
+    select.single = select.menu;
+    
+    
+    select.multiple = function(opts){
+        opts = getObject(opts);
+        opts.element = opts.element || {};
+        opts.element.multiple = true;
+        return select.menu(opts);
+    };
+    
+    // this script has loaded
+    select.loaded = true;
+
+    return XNAT.ui.select = select;
+
+}));
diff --git a/src/main/webapp/scripts/xnat/ui/tab.js b/src/main/webapp/scripts/xnat/ui/tab.js
new file mode 100644
index 0000000000000000000000000000000000000000..444eb93e2075c280a3710ce034379f5be8c73252
--- /dev/null
+++ b/src/main/webapp/scripts/xnat/ui/tab.js
@@ -0,0 +1,27 @@
+/*!
+ * Functions for creating XNAT tab UI elements
+ */
+
+var XNAT = getObject(XNAT||{});
+
+(function(factory){
+    if (typeof define === 'function' && define.amd) {
+        define(factory);
+    }
+    else if (typeof exports === 'object') {
+        module.exports = factory();
+    }
+    else {
+        return factory();
+    }
+}(function(){
+
+    var tab;
+
+    // just one tab
+    XNAT.ui.tab = tab =
+        getObject(XNAT.ui.tab || {});
+
+
+
+}));
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..5d78860113588cbace0f1d5ffe14415662a05411
--- a/src/main/webapp/scripts/xnat/ui/table.js
+++ b/src/main/webapp/scripts/xnat/ui/table.js
@@ -2,14 +2,47 @@
  * 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,
         undefined;
 
+    // add new element class without destroying existing class
+    function addClassName(el, newClass){
+        el.className = [].concat(el.className||[], newClass).join(' ').trim();
+        return el.className;
+    }
+
+    // add new data object item to be used for [data-] attribute(s)
+    function addDataObjects(obj, attrs){
+        obj.data = obj.data || {};
+        forOwn(attrs, function(name, prop){
+            obj.data[name] = prop;
+        });
+        return obj.data;
+    }
+
     /**
      * Constructor function for XNAT.table()
      * @param [opts] {Object} < table > Element attributes
@@ -18,8 +51,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 +64,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){
@@ -43,10 +76,6 @@ var XNAT = getObject(XNAT||{});
         this._rows = [];
         this.cols = this.columns = [];
 
-        // try to init?
-        if (config && config.data){
-            this.init(config.data);
-        }
     }
 
 
@@ -71,30 +100,34 @@ var XNAT = getObject(XNAT||{});
     });
 
     // create a single <td> element
-    Table.p.td = function(content, opts){
-        var td = element('td', opts||content, content);
+    // just using a single argument
+    // if you want to modify the <td>
+    // you'll need to pass a config
+    // object to set the properties
+    // and use append or innerHTML
+    // to add the cell content
+    Table.p.td = function(content){
+        var td = element('td', content);
         this.last.tr.appendChild(td);
-        //this.setLast(td);
         return this;
     };
 
-    Table.p.th = function(content, opts){
-        var th = element('th', opts||content, content);
+    Table.p.th = function(content){
+        var th = element('th', content);
         this.last.tr.appendChild(th);
-        //this.setLast(th);
         return this;
     };
 
-    Table.p.tr = function(data, opts){
+    Table.p.tr = function(opts, data){
         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 +143,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 +152,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 +167,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 +193,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;
     };
 
@@ -205,6 +238,10 @@ var XNAT = getObject(XNAT||{});
         return $(this.table);
     };
 
+    Table.p.getHTML = function(){
+        return this.table.outerHTML;
+    };
+
     /**
      * Populate table with data
      * @param data {Array} array of row arrays
@@ -213,26 +250,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 +284,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 +298,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,10 +313,15 @@ var XNAT = getObject(XNAT||{});
         return this;
 
     };
-    
-    Table.p.render = function(element){
-        if (element){
-            $$(element).empty().append(this.table);
+
+    Table.p.render = function(element, empty){
+        var $element;
+        if (element) {
+            $element = $$(element);
+            if (empty){
+                $element.empty();
+            }
+            $element.append(this.table);
         }
         return this.table;
     };
@@ -286,47 +330,60 @@ var XNAT = getObject(XNAT||{});
         return this.table.outerHTML;
     };
 
-    table = function(data, opts){
-        if (!opts){
-            opts = data;
-            data = [];
-        }
-        return new Table(data, opts);
+    // 'opts' are options for the <table> element
+    // 'config' is for other configurable stuff
+    table = function(opts, config){
+        return new Table(opts, config);
     };
-    
+
     // helper for future XNAT DataTable widget
     table.dataTable = function(data, opts){
-            
+        var tableData = data;
+        // tolerate reversed arguments
+        if (Array.isArray(opts)){
+            tableData = opts;
+            opts = data;
+        }
+        addClassName(opts, 'data-table');
+        var newTable = new Table(opts);
+        return newTable.init(tableData);
     };
 
     // table with <input> elements in the cells
     table.inputTable = function(data, opts){
-        data = data.map(function(row){
+        var tableData = data;
+        // tolerate reversed arguments
+        if (Array.isArray(opts)){
+            tableData = opts;
+            opts = data;
+        }
+        tableData = tableData.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);
             });
         });
         opts = getObject(opts);
-        opts.className = 'input-table';
-        var table = new Table(opts);
-        return table.init(data);
+        addClassName(opts, 'input-table');
+        var newTable = new Table(opts);
+        return newTable.init(tableData);
     };
 
-    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..d750f8bbb28674a5ba24a5c7f1c9283cf8d2c178
--- a/src/main/webapp/scripts/xnat/ui/tabs.js
+++ b/src/main/webapp/scripts/xnat/ui/tabs.js
@@ -2,332 +2,212 @@
  * Functions for creating XNAT tab UI elements
  */
 
-var XNAT = getObject(XNAT||{});
+var XNAT = getObject(XNAT || {});
 
-(function(XNAT, $, window, undefined){
-
-    var ui, tabs, page,
-        element = XNAT.element;
-
-    var $body = $(document.body);
-
-    XNAT.ui = ui = getObject(XNAT.ui || {});
-    XNAT.ui.tabs = XNAT.tabs = tabs = getObject(XNAT.ui.tabs || {});
-    XNAT.page = page = getObject(XNAT.page || {});
-
-
-    /**
-     * Initialize the tabs
-     * @param [tabsArray] {Array} array of tab config objects
-     * @param [container] {Element} parent element for tabs
-     * @returns {{}}
-     */
-    function init(tabsArray, container){
-
-        // a place to store things locally
-        var __ = {};
-
-        // keep tabs on ALL tabs
-        __.tabs = {};
-
-        // keep tabs on _each_ tab
-        __.tab = {};
-
-
-        // __.tab['tabName'].name    = 'name';
-        // __.tab['tabName'].label   = 'Label';
-        // __.tab['tabName'].id      = 'id'; // is this redundant?
-        // __.tab['tabName'].flipper = spawn('li.tab');
-        // __.tab['tabName'].content = spawn('div.tab-pane-content', content);
-        // __.tab['tabName'].pane    = spawn('div.tab-pane', [['div.pad', __.tab['tabName'].content]]);
+(function(factory){
+    if (typeof define === 'function' && define.amd) {
+        define(factory);
+    }
+    else if (typeof exports === 'object') {
+        module.exports = factory();
+    }
+    else {
+        return factory();
+    }
+}(function(){
+
+    var ui, tab, tabs, page;
+    
+    XNAT.ui = ui =
+        getObject(XNAT.ui || {});
+
+    XNAT.ui.tab = XNAT.tab = tab =
+        getObject(XNAT.ui.tab || XNAT.tab || {});
+
+    XNAT.ui.tabs = XNAT.tabs = tabs =
+        getObject(XNAT.ui.tabs || XNAT.tabs || {});
+
+    XNAT.page = page =
+        getObject(XNAT.page || {});
+
+
+    // ==================================================
+    // SET UP ONE TAB GROUP
+    // add a single tab group to the groups
+    tab.group = function(obj, container){
+        var id = toDashed(obj.id || obj.name);
+        if (!id) return; // a tab group MUST have an id
+        var group = spawn('ul.nav.tab-group', { id: id }, [
+            ['li.label', (obj.label || obj.title || obj.text || 'Tab Group')]
+        ]);
+        if (container) {
+            $$(container).append(group);
+        }
+        return group;
+    };
+    // ==================================================
+
+
+    // ==================================================
+    // SET UP TAB GROUPS
+    tab.groups = function(obj, container, empty){
+        var groups = [],
+            $container = $$(container);
+        $.each(obj, function(name, label){
+            groups.push(tab.group({
+                id: toDashed(name),
+                label: label
+            }));
+        });
+        if (empty) {
+            $container.empty();
+        }
+        $container.append(groups);
+        return groups;
+    };
+    // ==================================================
 
 
-        function activateTab(name){
+    // save the id of the active tab
+    XNAT.ui.tab.active = '';
 
-            var tab = __.tab[name];
+    function activateTab(tab, id){
 
-            // first deactivate ALL tabs and panes
+        var $tab  = $(tab),
+            $tabs = $(tab).closest('div.xnat-tab-container');
 
-            __.tabs.$panes
-              .find('.tab-pane')
-              .hide()
-              .removeClass('active');
+        // first deactivate ALL tabs and panes
+        $tabs
+            .find('div.tab-pane')
+            .hide()
+            .removeClass('active');
 
-            __.tabs.$flippers
-              .find('.tab')
-              .removeClass('active');
+        $tabs
+            .find('li.tab')
+            .removeClass('active');
 
-            // then activate THIS tab and pane
+        // then activate THIS tab and pane
 
-            $(tab.flipper.wrapper)
-                .addClass('active');
+        $tab.addClass('active');
 
-            $(tab.pane.wrapper)
-                .show()
-                .addClass('active');
+        $('#' + id)
+            .show()
+            .addClass('active');
 
-            __.tabs.activeTab = name;
+        XNAT.ui.tab.active = id;
 
-        }
+    }
+    
 
+    // ==================================================
+    // CREATE A SINGLE TAB
+    tab.init = function _tab(obj){
 
-        function refreshData(form, url){
-            
-        }
+        var $group, _flipper, _pane;
 
+        obj = getObject(obj);
+        obj.config = getObject(obj.config);
+        obj.config.id = obj.config.id || obj.id || (toDashed(obj.name) + '-content');
+        obj.config.data = extend({ name: obj.name }, obj.config.data);
 
+        _flipper = spawn('li.tab', {
+            // onclick event handler attached
+            // directly to tab flipper
+            onclick: function(){
+                activateTab(this, obj.config.id)
+            }
+        }, [
+            ['a', {
+                title: obj.label,
+                // href: '#'+obj.config.id,
+                href: '#!',
+                html: obj.label
+            }]
+        ]);
+
+        // setup the footer for the whole tab pane
         function paneFooter(){
-
-            var footer = spawn('footer.footer', [
+            return spawn('footer.footer', [
                 ['button', {
                     type: 'button',
                     html: 'Save All',
                     classes: 'save-all btn btn-primary pull-right'
                 }]
             ]);
-
-            return footer;
-
         }
+        tab.paneFooter = paneFooter;
 
+        _pane = spawn('div.tab-pane', obj.config);
 
-        // spawn sample elements for a pane
-        function sampleContent(name, content){
-            var pane = __.tab[name].pane.content;
-            var list = [];
-            [].concat(content).forEach(function(item){
-                list.push(spawn('li', {
-                    innerHTML: item.label + ': ',
-                    id: item.id,
-                    data: {
-                        name: item.name,
-                        kind: item.kind
-                    },
-                    append: element.p(item.description||'')
-                }))
-            });
-            pane.appendChild(spawn('ul', list));
+        if (obj.active) {
+            $(_flipper).addClass('active');
+            $(_pane).addClass('active');
+            tab.active = _pane.id;
         }
 
-
-        // add content to pane
-        function paneContents(contents){
-            return [].concat(contents).map(function(item){
-                // "kind" property defines which
-                // kind of UI widget to render
-                // default is "panel"
-                var widget = item.kind||'panel';
-                return ui[widget](item).element;
-            });
+        // un-hide the group that this tab is in
+        // (groups are hidden until there is a tab for them)
+        $group = $('#' + (toDashed(obj.group || 'other')) + '.tab-group');
+        
+        $group.show();
+        
+        // add all the flippers
+        $group.append(_flipper);
+
+        function render(element){
+            $$(element).append(_pane);
+            $$(element).append(paneFooter());
+            return _pane;
         }
 
-
-        // set up a single pane
-        function setupPane(name){
-            var tab = __.tab[name];
-            tab.pane = {};
-            tab.pane.content = spawn('div.tab-pane-content');
-            tab.pane.wrapper = spawn('div.tab-pane', {
-                id: tab.id + '-content'
-            }, [
-                ['div.pad', [tab.pane.content]]
-            ]);
-
-            // add pane header
-            // or don't...
-            //tab.pane.content.appendChild(element.h3(tab.label));
-
-            // add contents
-            if (tab.contents){
-                $(tab.pane.content).append(paneContents(tab.contents));
-            }
-
-            //sampleContent(tab.name, tab.contents||[]);
-
-            // add styles so footer is pinned at the bottom
-            // move these to CSS eventually
-            tab.pane.wrapper.style.position = 'relative';
-            tab.pane.wrapper.style.paddingBottom = '60px';
-
-            // append footer last
-            tab.pane.wrapper.append(paneFooter());
-
-            return tab.pane;
+        function get(){
+            return _pane;
         }
 
-
-        // create <ul> elements for tab groups
-        function setupGroups(list){
-
-            var flippers = __.tabs.flippers.container;
-
-            __.tabs.groups = {};
-
-            list.forEach(function(item){
-
-                var name = item.name;
-                var label = item.label;
-                var id = toDashed(name) + '-tabs';
-
-                var container = spawn('ul.nav.tab-group', {
-                    id: id,
-                    html: '<li class="label">' + label + '</li>'
-                });
-
-                flippers.appendChild(container);
-
-                __.tabs.groups[name] = {
-                    name: name,
-                    label: label,
-                    id: id,
-                    container: container
-                };
-
-            });
-        }
-
-
-        // set up a single flipper
-        function setupFlipper(name){
-            var tab = __.tab[name];
-            tab.flipper = {};
-            tab.flipper.wrapper = spawn('li.tab');
-            tab.flipper.label =
-                tab.flipper.a =
-                    spawn('a', {
-                        innerHTML: tab.label,
-                        title: tab.label,
-                        href: '#' + tab.id,
-                        onclick: function(){
-                            activateTab(name);
-                        }
-            });
-            if (tab.isDefault && !__.tabs.activeTab){
-                activateTab(name);
-            }
-            tab.flipper.wrapper.appendChild(tab.flipper.label);
-            return tab.flipper;
+        return {
+            // contents: obj.tabs||obj.contents||obj.content||'',
+            flipper: _flipper,
+            pane: _pane,
+            element: _pane,
+            spawned: _pane,
+            render: render,
+            get: get
         }
+    };
+    // ==================================================
 
 
-        /**
-         * Process JSON and setup flippers and panes to render
-         * @param config {Array} array of tabs
-         */
-        function setupTabs(config){
-
-            var frag = spawn.fragment();
-            var panes = $$('!div.xnat-tab-content'); // get existing element
-            //var panes = spawn('div.xnat-tab-content.xnat-tab-panes');
-            //var panes = element.div({className:'xnat-tab-content xnat-tab-panes'});
-            var flippers = $$('!div.xnat-nav-tabs'); // get existing element
-            //var flippers = spawn('ul.nav.xnat-nav-tabs');
-            //var flippers = element.ul({className:'nav xnat-nav-tabs'});
-
-            // expose to outer scope
-            __.tabs.frag = frag;
-            __.tabs.panes = {
-                container: panes
-            };
-            __.tabs.flippers = {
-                container: flippers
-            };
-            __.tabs.config = config;
-
-            __.tabs.$panes = $(panes);
-            __.tabs.$flippers = $(flippers);
-
-            function setLayout(side){
-                // only 'left' or 'right'
-                if (!/left|right/.test(side)) return;
-                var other = side === 'left' ? 'right' : 'left';
-                __.tabs.$flippers.addClass('side pull-'+side);
-                __.tabs.$panes.addClass('side pull-'+other);
-            }
-
-            [].concat(config).forEach(function(item){
-
-                if (item.kind !== 'tab') {
-                    if (item.kind === 'meta'){
-                        if (item.groups){
-                            // setup tab groups
-                            setupGroups(item.groups);
-                        }
-                        if (item.layout){
-                            setLayout(item.layout);
-                        }
-                    }
-                    return;
-                }
+    // ==================================================
+    // MAIN FUNCTION
+    tabs.init = function _tabs(obj){
 
-                var _tab = extend(true, {}, item);
-                var _name = item.name || randomID('tab-', false);
+        var spawned = spawn('div.tabs');
 
-                _tab.name = _name;
-                _tab.label = item.label || titleCase(_name);
-                _tab.id = item.id || toDashed(_name);
-
-                // save the first tab to activate it if there's no 'active' tab
-                if (!__.tabs.firstTab){
-                    __.tabs.firstTab = _name;
-                }
-
-                // add THIS tab to the collection
-                __.tab[_name] = _tab;
-
-                _tab.pane = setupPane(_name);
-                _tab.flipper = setupFlipper(_name);
-
-                __.tabs.panes.container.appendChild(_tab.pane.wrapper);
-                __.tabs.groups[item.group].container.appendChild(_tab.flipper.wrapper);
-
-            });
-
-            // set the first tab to active if no 'default' tab is set
-            if (!__.tabs.activeTab){
-                activateTab(__.tabs.firstTab)
-            }
-
-            frag.appendChild(flippers);
-            frag.appendChild(panes);
-
-            return __;
+        // set up the group elements
+        tab.groups(obj.meta.tabGroups, '#admin-config-tabs > .xnat-nav-tabs');
 
+        function render(element){
+            $$(element).append(spawned);
+            return spawned;
         }
-        // expose globally
-        __.setup = setupTabs;
 
-        // run setup on init() if 'tabsArray' is present
-        if (tabsArray && tabsArray.length){
-            setupTabs(tabsArray);
+        function get(){
+            return spawned;
         }
 
-        __.render = function(container){
-            $$(container).append(__.tabs.frag);
-            // clone values
-            //$('[value^="@|"]').each(function(){
-            //    var selector = $(this).val().split('@|')[1];
-            //    var value = $$(selector).val();
-            //    $(this).val(value).dataAttr('value',value);
-            //    $(this).change();
-            //});
-            return __;
+        return {
+            // contents: obj.tabs||obj.contents||obj.content||'',
+            element: spawned,
+            spawned: spawned,
+            render: render,
+            get: get
         };
 
-        // render immediately if 'container' is specified
-        if (container){
-            __.render(container);
-        }
-
-        // object to cache tab elements and data for quicker access
-        XNAT.page.tabs = __.tab;
-
-        return __;
-
-    }
-
-    tabs.init = init;
+    };
+    // ==================================================
 
-})(XNAT, jQuery, window);
+    tabs.tab = tab;
 
+    return tabs;
 
+}));
diff --git a/src/main/webapp/scripts/xnat/ui/templates.js b/src/main/webapp/scripts/xnat/ui/templates.js
new file mode 100644
index 0000000000000000000000000000000000000000..e61089a4ceeadf7268e1dd12355d5a4da50f3878
--- /dev/null
+++ b/src/main/webapp/scripts/xnat/ui/templates.js
@@ -0,0 +1,333 @@
+/*!
+ * Templates for creating UI elements with spawn.js
+ */
+
+var XNAT = getObject(XNAT);
+
+(function(factory){
+    if (typeof define === 'function' && define.amd) {
+        define(factory);
+    }
+    else if (typeof exports === 'object') {
+        module.exports = factory();
+    }
+    else {
+        return factory();
+    }
+}(function(){
+
+    var undefined, template,
+        $ = jQuery || null; // check and localize
+
+    XNAT.ui = getObject(XNAT.ui || {});
+
+    XNAT.ui.template = template = 
+        XNAT.ui.template || {};
+
+    // add new element class without destroying existing class
+    function addClassName(el, newClass){
+        el.className = [].concat(el.className||[], newClass).join(' ').trim();
+        return el.className;
+    }
+
+    // add new data object item to be used for [data-] attribute(s)
+    function addDataObjects(obj, attrs){
+        obj.data = obj.data || {};
+        forOwn(attrs, function(name, prop){
+            obj.data[name] = prop;
+        });
+        return obj.data;
+    }
+
+    function lookupValue(el, lookup){
+        var val = '';
+        try {
+            val = eval(lookup.trim());
+        }
+        catch (e) {
+            val = '';
+            console.log(e);
+        }
+        // changeVal() changes the value and triggers
+        // the 'onchange' event
+        $(el).changeVal(val).dataAttr('value', val);
+        return val;
+    }
+
+    // another way to do this without using eval()
+    // is to loop over object string using dot notation:
+    // var myVal = lookupObjectValue(XNAT, 'data.siteConfig.siteId');
+    // --> myVal == 'myXnatSiteId'
+    function lookupObjectValue(root, objStr){
+        var val = '';
+        if (!objStr) {
+            objStr = root;
+            root = window;
+        }
+        root = root || window;
+        objStr.toString().trim().split('.').forEach(function(part, i){
+            // start at the root object
+            if (i === 0) {
+                val = root[part] || {};
+            }
+            else {
+                val = val[part];
+            }
+        });
+        return val;
+    }
+
+
+    // ========================================
+    // subhead element to segment panels
+    template.panelSubhead = function(opts){
+        var _templ, _spawn, _html;
+        opts = cloneObject(opts);
+        _templ = ['h4.panel-subhead', opts];
+        _spawn = function(){
+            return spawn.apply(null, _templ);
+        };
+        _html = _spawn().outerHTML;
+        return {
+            template: _templ,
+            spawned: _spawn(),
+            spawn: _spawn,
+            html: _html,
+            get: _spawn
+        }
+    };
+    // ========================================
+
+    
+    // ========================================
+    // generic panel element
+    template.panelElement = function(opts, content){
+        var _templ, _spawn, _html;
+        opts = cloneObject(opts);
+        addClassName(opts, 'panel-element');
+        _templ = [
+            'div|data-name='+(opts.name||''),
+            { className: opts.className },
+            content
+        ];
+        _spawn = function(){
+            return spawn.apply(null, _templ);
+        };
+        _html = _spawn().outerHTML;
+        return {
+            template: _templ, // the raw template (Spawn array)
+            spawned: _spawn(), // pre-spawned
+            spawn: _spawn, // call to make a fresh spawn
+            html: _html, // pre-spawned HTML
+            get: _spawn,
+            getHTML: function(){ // call to get fresh HTML
+                return _spawn().outerHTML;
+            }
+        }
+    };
+    // ========================================
+
+
+    // ========================================
+    // display only element for form panels
+    template.panelDisplay = function(opts, element){
+        
+        opts = cloneObject(opts);
+        opts.id = opts.id||toDashed(opts.name||'');
+        opts.label = opts.label||opts.title||opts.name||'';
+        
+        // pass in an element or create a new 'div' element
+        element = 
+            element || spawn('div', {
+                id: opts.id,
+                className: opts.className||'',
+                title: opts.title||opts.name||opts.id,
+                html: opts.value||opts.html||opts.text||opts.body||''
+            });
+        
+        return template.panelElement(opts, [
+            ['label.element-label|for='+element.id||opts.id, opts.label],
+            ['div.element-wrapper', [
+                
+                element ,
+                
+                ['div.description', opts.description]
+            ]]
+        ]);
+    };
+    // ========================================    
+
+
+    // ========================================
+    // input element for form panels
+    template.panelInput = function(opts, element){
+        opts = cloneObject(opts);
+        opts.name = opts.name || opts.id || randomID('input-', false);
+        opts.id = opts.id||toDashed(opts.name||'');
+        opts.label = opts.label||opts.title||opts.name||'';
+        opts.element = extend({
+            type: opts.type||'text',
+            id: opts.id,
+            name: opts.name,
+            className: opts.className||'',
+            size: opts.size || 25,
+            title: opts.title||opts.name||opts.id,
+            value: opts.value||''
+        }, opts.element);
+
+        opts.data = opts.data || {};
+        
+        if (opts.element.type !== 'password'){
+            opts.data.value = opts.data.value || opts.value;
+        }
+
+        addDataObjects(opts.element, opts.data||{});
+        
+        if (opts.placeholder) {
+            opts.element.placeholder = opts.placeholder;
+        }
+
+        // use an existing element (passed as the second argument)
+        // or spawn a new one
+        element = element || spawn('input', opts.element);
+
+        if (/checkbox|radio/i.test(opts.type||'') && opts.checked) {
+            element.checked = true;
+        }
+
+        // set the value of individual form elements
+        
+        // look up a namespaced object value if the value starts with '??'
+        var doLookup = '??';
+        if (opts.value && opts.value.toString().indexOf(doLookup) === 0) {
+            element.value = lookupObjectValue(opts.value.split(doLookup)[1]);
+        }
+        
+        if (opts.load) {
+            if (opts.load.lookup) {
+                lookupValue(element, opts.load.lookup);
+            }
+            else if (opts.load.url){
+                $.ajax({
+                    method: opts.load.method || 'GET',
+                    url: XNAT.url.restUrl(opts.load.url),
+                    success: function(data){
+                        // get value from specific object path
+                        if (opts.load.prop) {
+                            opts.load.prop.split('.').forEach(function(part){
+                                data = data[part] || {};
+                            });
+                            // data = lookupObjectValue(opts.load.prop);
+                        }
+                        $(element).changeVal(data).dataAttr('value', data);
+                    }
+                })
+            }
+        }
+
+        return template.panelElement(opts, [
+            ['label.element-label|for='+element.id||opts.id, opts.label],
+            ['div.element-wrapper', [
+
+                element,
+
+                ['div.description', opts.description||opts.body||opts.html]
+            ]]
+        ]);
+    };
+    // ========================================
+
+
+    // ========================================
+    // select element for form panels
+    template.panelSelect = function(opts){
+        opts = cloneObject(opts);
+        opts.name = opts.name || opts.id || randomID('select-', false);
+        opts.id = opts.id || toDashed(opts.name||'');
+        opts.element = extend({
+            id: opts.id,
+            name: opts.name,
+            className: opts.className||'',
+            //size: 25,
+            title: opts.title||opts.name||opts.id||'',
+            value: opts.value||''
+        }, opts.element);
+
+        var _select = spawn('select', opts.element, [['option|value=!', 'Select']]);
+        
+        // add the options
+        $.each(opts.options||{}, function(name, prop){
+            var _option = spawn('option', extend(true, {
+                html: prop.label || prop.value || prop,
+                value: prop.value || name
+            }, prop.element));
+            // select the option if it's the select element's value
+            if (prop.value === opts.value){
+                _option.selected = true;
+            }
+            _select.appendChild(_option)    
+        });
+        return template.panelInput(opts, _select);
+    };
+    // ========================================
+
+
+    template.panelElementGroup = function(opts, elements){
+        opts = cloneObject(opts);
+        return template.panelElement(opts, [
+            ['label.element-label|for='+opts.id, opts.label||opts.title||opts.name],
+            ['div.element-wrapper', elements]
+        ]);
+    };
+    
+    
+    template.codeEditor = function(opts, contents){
+        // options for the 'div.editor-content' element
+        opts = extend(true, opts, {
+            style: {
+                width: '100%', height: '100%',
+                position: 'absolute',
+                top: 0, right: 0,
+                bottom: 0, left: 0,
+                border: '1px solid #ccc'
+            }
+        });
+        // don't pass 'before' and 'after' into the editor
+        var before = opts.before || '';
+        var after = opts.after || '';
+        delete opts.before;
+        delete opts.after;
+        var content = spawn('div.editor-content', opts, contents||'');
+        var _tmpl = ['form.code-editor', [
+            before,
+            ['div.editor-wrapper', {
+                style: {
+                    width:  opts.width  || '840px',
+                    height: opts.height || '482px',
+                    position: 'relative'
+                }
+            }, [content]],
+            after
+        ]];
+        var _spawned = spawn.apply(null, _tmpl);
+        var _html = _spawned.outerHTML;
+        return {
+            template: _tmpl, // the raw template (Spawn array)
+            spawned: _spawned, // pre-spawned
+            editor: content, // easy-to-remember name for the editor div
+            target: content, // for inserting content dynamically
+            inner: content,
+            html: _html, // pre-spawned HTML
+            outerHTML: _html,
+            get: function(){
+                return _spawned;
+            },
+            getHTML: function(){ // call to get the HTML
+                return _html;
+            }
+        }
+    };
+
+    return XNAT.ui.templates = XNAT.ui.template = template;
+
+}));
diff --git a/src/main/webapp/setup/index.jsp b/src/main/webapp/setup/index.jsp
new file mode 100644
index 0000000000000000000000000000000000000000..6844ccedeef8101d76db7e8f27ac65733dc5cd77
--- /dev/null
+++ b/src/main/webapp/setup/index.jsp
@@ -0,0 +1,83 @@
+<%@ 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" %>
+
+<pg:xnat page="setup">
+
+    <div id="page-body">
+        <div class="pad">
+
+            <div id="setup-page">
+
+                <c:set var="message">
+                    <header id="content-header">
+                        <h2>XNAT Site Setup</h2>
+                        <div class="message">
+                            This XNAT system has not yet been configured for use.
+                            Please contact your site administrator to have the system set up.
+                        </div>
+                    </header>
+                </c:set>
+
+                <pg:restricted msg="${message}">
+
+                    <c:import url="/xapi/siteConfig" var="siteConfig"/>
+
+                    <script>
+                        XNAT.data = extend({}, XNAT.data, {
+                            siteConfig: ${siteConfig}
+                        });
+                        // get rid of the 'targetSource' property
+                        delete XNAT.data.siteConfig.targetSource;
+                    </script>
+                    <script src="${sessionScope.siteRoot}/scripts/xnat/spawner.js"></script>
+
+                    <header id="content-header">
+                        <h2 class="pull-left">XNAT Site Setup</h2>
+                        <div class="hidden message pull-left">
+                            The settings below need to be configured before this XNAT system
+                            can be used. Please set the properties below and submit the form continue.
+                        </div>
+                        <div class="clearfix"></div>
+                    </header>
+
+                    <!-- Setup tab container -->
+                    <div id="site-setup-panels">
+
+
+                        <!-- ======================== -->
+                        <!-- PANELS WILL SHOW UP HERE -->
+                        <!-- ======================== -->
+
+
+                    </div>
+                    <!-- /#site-setup-panels -->
+
+
+                    <script>
+
+                        XNAT.xhr.get({
+                            url: XNAT.url.rootUrl('/page/admin/data/config/site-setup.yaml'),
+                            //url: XNAT.url.rootUrl('/xapi/spawner/resolve/siteAdmin/siteSetup'),
+                            success: function(data){
+                                if (typeof data === 'string') {
+                                    data = YAML.parse(data);
+                                }
+                                var setupPanels = XNAT.spawner.spawn(data);
+                                setupPanels.render('#site-setup-panels');
+                            }
+                        });
+
+                    </script>
+
+
+                </pg:restricted>
+
+            </div>
+            <!-- /#setup-page -->
+
+        </div>
+    </div>
+    <!-- /#page-body -->
+
+</pg:xnat>
diff --git a/src/main/webapp/xnat-templates/navigations/AppJS.vm b/src/main/webapp/xnat-templates/navigations/AppJS.vm
index 21060b3c0c50950d3bf850b9b1116f62e60c2464..8facd5b02123c9b3062d2f9a49dd68deb2244606 100644
--- a/src/main/webapp/xnat-templates/navigations/AppJS.vm
+++ b/src/main/webapp/xnat-templates/navigations/AppJS.vm
@@ -6,6 +6,8 @@
     <!-- app.css loaded last -->
     <link rel="stylesheet" type="text/css" href="$content.getURI('style/app.css?v=1.7.0a1')">
 
+    <link rel="stylesheet" type="text/css" href="$content.getURI('page/admin/style.css?v=1.7.0a1')">
+
     <link rel="stylesheet" type="text/css" href="$content.getURI('scripts/xmodal-v1/xmodal.css?v=1.7.0a1')">
     <script src="$content.getURI('scripts/xmodal-v1/xmodal.js')"></script>
     <script src="$content.getURI('scripts/xmodal-v1/xmodal-migrate.js')"></script>
diff --git a/src/main/webapp/xnat-templates/navigations/DefaultTop.vm b/src/main/webapp/xnat-templates/navigations/DefaultTop.vm
index 529763fc2ea5a671061ad6a00467b0b89e2f7582..4b7abb72ade59f746275babfa2eb83a1839be39a 100644
--- a/src/main/webapp/xnat-templates/navigations/DefaultTop.vm
+++ b/src/main/webapp/xnat-templates/navigations/DefaultTop.vm
@@ -99,7 +99,7 @@
 <div id="main_nav">
     <div class="inner">
 
-    <ul class="nav" style="border-left: 1px solid #033769;">
+    <ul class="nav">
         #addGlobalCustomScreens("topBar")
     </ul>