[Bps-public-commit] rt-extension-turbo branch master created. 03c2b8068dea8246eebeee3ba749f9ac22d580a6

BPS Git Server git at git.bestpractical.com
Mon Aug 1 19:33:45 UTC 2022


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "rt-extension-turbo".

The branch, master has been created
        at  03c2b8068dea8246eebeee3ba749f9ac22d580a6 (commit)

- Log -----------------------------------------------------------------
commit 03c2b8068dea8246eebeee3ba749f9ac22d580a6
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Mon Aug 1 15:22:09 2022 -0400

    Add ToDo list

diff --git a/lib/RT/Extension/Turbo.pm b/lib/RT/Extension/Turbo.pm
index 7ef5f4c..13e21d5 100644
--- a/lib/RT/Extension/Turbo.pm
+++ b/lib/RT/Extension/Turbo.pm
@@ -45,6 +45,36 @@ Add this line:
 
 =back
 
+=head1 ToDo
+
+=over
+
+=item *
+
+Make it a user-level and global config option to switch homepages
+
+=item *
+
+Add a timer at debug level in logs per component
+
+=item *
+
+Fix issue with reloading dropdowns on refresh
+
+=item *
+
+Get forms working (Quickcreate)
+
+=item *
+
+Add auto-refresh feature per portlet
+
+=item *
+
+Add a loading icon for possible slow portlet loads
+
+=back
+
 =head1 AUTHOR
 
 Best Practical Solutions, LLC E<lt>modules at bestpractical.comE<gt>
commit 26adf630175e69bed85952ee309b44736d44809d
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Mon Aug 1 15:09:55 2022 -0400

    Render components and saved searches with turbo

diff --git a/html/Callbacks/RT-Extension-Turbo/autohandler/Final b/html/Callbacks/RT-Extension-Turbo/autohandler/Final
new file mode 100644
index 0000000..0e8e714
--- /dev/null
+++ b/html/Callbacks/RT-Extension-Turbo/autohandler/Final
@@ -0,0 +1,8 @@
+<%init>
+
+# The only thing after this callback is the default RT footer
+# Abort here for Views requests to avoid outputting the footer
+if ( $m->request_path =~ /^\/Views/ ) {
+    $m->abort;
+}
+</%init>
diff --git a/html/Elements/MyTurbo b/html/Elements/MyTurbo
index 7731812..e1560ae 100644
--- a/html/Elements/MyTurbo
+++ b/html/Elements/MyTurbo
@@ -1,5 +1,114 @@
 <div class="myrt row">
+<div class="<% 'boxcontainer col-md-' . ( $sidebar ? '8' : '12' ) %>">
+% foreach my $entry ( @$body ) {
+%   $show_cb->($entry);
+% }
+</div>
 
-<p>Hello</p>
-
+% if ( $sidebar ) {
+<div class="boxcontainer col-md-4">
+% foreach my $entry ( @$sidebar ) {
+%   $show_cb->($entry);
+% }
 </div>
+% }
+</div>
+
+<%init>
+# All lifted from MyRT in core RT
+my %allowed_components = map {$_ => 1} @{RT->Config->Get('HomepageComponents')};
+
+my $user = $session{'CurrentUser'}->UserObj;
+unless ( $Portlets ) {
+    my ($system_default) = RT::System->new($session{'CurrentUser'})->Attributes->Named('DefaultDashboard');
+    my $system_default_id = $system_default ? $system_default->Content : 0;
+    my $dashboard_id = $user->Preferences( DefaultDashboard => $system_default_id ) or return;
+
+    # Allow any user to read system default dashboard
+    my $dashboard = RT::Dashboard->new($system_default_id == $dashboard_id ? RT->SystemUser : $session{'CurrentUser'});
+    my ( $ok, $msg ) = $dashboard->LoadById( $dashboard_id );
+    if ( !$ok ) {
+        my $user_msg = loc('Unable to load selected dashboard, it may have been deleted');
+        if ( $dashboard_id == $system_default_id ) {
+            RT->Logger->warn("Unable to load dashboard: $msg");
+            $m->out($m->scomp('/Elements/ListActions', actions => $user_msg));
+            return;
+        }
+        else {
+            my ( $ok, $sys_msg ) = $dashboard->LoadById( $system_default_id );
+            if ( $ok ) {
+                $m->out($m->scomp('/Elements/ListActions', actions => [$user_msg, loc('Setting homepage to system default homepage')]));
+                my ( $ok, $msg ) = $user->DeletePreferences( 'DefaultDashboard' );
+                RT->Logger->error( "Couldn't delete DefaultDashboard of user " . $user->Name . ": $msg" ) unless $ok;
+            }
+            else {
+                RT->Logger->warn("Unable to load dashboard: $msg $sys_msg");
+                $m->out($m->scomp('/Elements/ListActions', actions => $user_msg));
+                return;
+            }
+        }
+    }
+    $Portlets = $dashboard->Panes;
+}
+
+$m->callback( CallbackName => 'MassagePortlets', Portlets => $Portlets );
+
+my ($body, $sidebar) = @{$Portlets}{qw(body sidebar)};
+unless( $body && @$body ) {
+    $body = $sidebar || [];
+    $sidebar = undef;
+}
+$sidebar = undef unless $sidebar && @$sidebar;
+
+my $Rows = $user->Preferences( 'SummaryRows', ( RT->Config->Get('DefaultSummaryRows') || 10 ) );
+
+my $show_cb;
+$show_cb = sub {
+    my $entry = shift;
+    my $type;
+    my $name;
+
+    # Normal handling for RT 5.0.2 and newer
+    my $depth = shift || 0;
+    Abort("Possible recursive dashboard detected.", SuppressHeader => 1) if $depth > 8;
+
+    $type  = $entry->{portlet_type};
+    $name = $entry->{component};
+    if ( $type eq 'component' ) {
+        if (!$allowed_components{$name}) {
+            $m->out( $m->interp->apply_escapes( loc("Invalid portlet [_1]", $name), "h" ) );
+            RT->Logger->info("Invalid portlet $name found on user " . $user->Name . "'s homepage");
+            if ($name eq 'QueueList' && $allowed_components{Quicksearch}) {
+                RT->Logger->warning("You may need to replace the component 'Quicksearch' in the HomepageComponents config with 'QueueList'. See the UPGRADING-4.4 document.");
+            }
+        }
+        else {
+            # Add turbo tags for components
+            $m->out('<turbo-frame id="component-' . lc($name) . '" src="/Views/Component/' . $name . '">');
+            $m->out('</turbo-frame>');
+        }
+    } elsif ( $type eq 'search' ) {
+            # Add turbo tags for saved searches
+        my ($saved_search, $search_name) = RT::Dashboard->ShowSearchName($entry);
+        $m->out('<turbo-frame id="savedsearch-' . lc($search_name) . '" src="/Views/Component/SavedSearch?SavedSearch=' . $search_name . '&Rows=' . $Rows . '">');
+        $m->out('</turbo-frame>');
+    } elsif ( $type eq 'dashboard' ) {
+        my $current_dashboard = RT::Dashboard->new($session{CurrentUser});
+        my ($ok, $msg) = $current_dashboard->LoadById($entry->{id});
+        if (!$ok) {
+            $m->out($msg);
+            return;
+        }
+        my @panes = @{ $current_dashboard->Panes->{$entry->{pane}} || [] };
+        for my $portlet (@panes) {
+            $show_cb->($portlet, $depth + 1);
+        }
+    } else {
+        $RT::Logger->error("unknown portlet type '$type'");
+    }
+};
+
+</%init>
+<%args>
+$Portlets => undef
+</%args>
diff --git a/html/Views/Component/dhandler b/html/Views/Component/dhandler
new file mode 100644
index 0000000..775f220
--- /dev/null
+++ b/html/Views/Component/dhandler
@@ -0,0 +1,25 @@
+% if ( $component_name eq 'SavedSearch' ) {
+<turbo-frame id="savedsearch-<%lc($ARGS{SavedSearch})%>">
+% $m->comp( "/Elements/ShowSearch", %ARGS );
+% } else {
+<turbo-frame id="component-<%$component_name%>">
+% $m->comp( "/Elements/$component_name" );
+% }
+</turbo-frame>
+
+<%init>
+my ($component_name) = $m->dhandler_arg;
+
+if ( $component_name eq 'SavedSearch' ) {
+    # Put Override args in the correct structure
+    $ARGS{Override} = {};
+    foreach my $override ( qw(Rows) ) {
+        if ( $ARGS{$override} ) {
+            $ARGS{Override}->{$override} = $ARGS{$override};
+            delete $ARGS{$override};
+        }
+    }
+}
+</%init>
+<%args>
+</%args>
diff --git a/html/turbo_home.html b/html/turbo_home.html
index 777bcae..17716c5 100644
--- a/html/turbo_home.html
+++ b/html/turbo_home.html
@@ -4,6 +4,5 @@
 <& /Elements/MyTurbo &>
 
 <%init>
-warn "Running turbo_home";
 my @results;
 </%init>
commit 12cb0adbe7dbff6ea2dc8eeaf63e61f3e560b533
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Thu Jul 28 13:52:00 2022 -0400

    Load Turbo and create a test home page

diff --git a/META.yml b/META.yml
index 018d6e1..68a1e53 100644
--- a/META.yml
+++ b/META.yml
@@ -16,6 +16,7 @@ meta-spec:
 name: RT-Extension-Turbo
 no_index:
   directory:
+    - html
     - inc
     - static
 requires:
diff --git a/html/Callbacks/RT-Extension-Turbo/index.html/Initial b/html/Callbacks/RT-Extension-Turbo/index.html/Initial
new file mode 100644
index 0000000..7aa375e
--- /dev/null
+++ b/html/Callbacks/RT-Extension-Turbo/index.html/Initial
@@ -0,0 +1,3 @@
+<%init>
+RT::Interface::Web::Redirect(RT->Config->Get('WebURL')."turbo_home.html");
+</%init>
diff --git a/html/Elements/MyTurbo b/html/Elements/MyTurbo
new file mode 100644
index 0000000..7731812
--- /dev/null
+++ b/html/Elements/MyTurbo
@@ -0,0 +1,5 @@
+<div class="myrt row">
+
+<p>Hello</p>
+
+</div>
diff --git a/html/turbo_home.html b/html/turbo_home.html
new file mode 100644
index 0000000..777bcae
--- /dev/null
+++ b/html/turbo_home.html
@@ -0,0 +1,9 @@
+<& /Elements/Header, Title => "RT at a glance" &>
+<& /Elements/Tabs &>
+<& /Elements/ListActions, actions => \@results &>
+<& /Elements/MyTurbo &>
+
+<%init>
+warn "Running turbo_home";
+my @results;
+</%init>
diff --git a/lib/RT/Extension/Turbo.pm b/lib/RT/Extension/Turbo.pm
index 5a7e693..7ef5f4c 100644
--- a/lib/RT/Extension/Turbo.pm
+++ b/lib/RT/Extension/Turbo.pm
@@ -4,6 +4,9 @@ package RT::Extension::Turbo;
 
 our $VERSION = '0.01';
 
+RT->AddJavaScript('turbo.es2017-umd.js');
+RT->AddJavaScript('rt-extension-turbo.js');
+
 =head1 NAME
 
 RT-Extension-Turbo - Experimental extension using Turbo
diff --git a/static/js/rt-extension-turbo.js b/static/js/rt-extension-turbo.js
new file mode 100644
index 0000000..38355bf
--- /dev/null
+++ b/static/js/rt-extension-turbo.js
@@ -0,0 +1,2 @@
+// Disable by default, then enable where needed
+Turbo.session.drive = false;
diff --git a/static/js/turbo.es2017-umd.js b/static/js/turbo.es2017-umd.js
new file mode 100644
index 0000000..07f69ed
--- /dev/null
+++ b/static/js/turbo.es2017-umd.js
@@ -0,0 +1,3362 @@
+/*
+Turbo 7.1.0
+Copyright © 2022 Basecamp, LLC
+ */
+(function (global, factory) {
+    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+    typeof define === 'function' && define.amd ? define(['exports'], factory) :
+    (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Turbo = {}));
+}(this, (function (exports) { 'use strict';
+
+    (function () {
+        if (window.Reflect === undefined || window.customElements === undefined ||
+            window.customElements.polyfillWrapFlushCallback) {
+            return;
+        }
+        const BuiltInHTMLElement = HTMLElement;
+        const wrapperForTheName = {
+            'HTMLElement': function HTMLElement() {
+                return Reflect.construct(BuiltInHTMLElement, [], this.constructor);
+            }
+        };
+        window.HTMLElement =
+            wrapperForTheName['HTMLElement'];
+        HTMLElement.prototype = BuiltInHTMLElement.prototype;
+        HTMLElement.prototype.constructor = HTMLElement;
+        Object.setPrototypeOf(HTMLElement, BuiltInHTMLElement);
+    })();
+
+    /**
+     * The MIT License (MIT)
+     * 
+     * Copyright (c) 2019 Javan Makhmali
+     * 
+     * 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(prototype) {
+      if (typeof prototype.requestSubmit == "function") return
+
+      prototype.requestSubmit = function(submitter) {
+        if (submitter) {
+          validateSubmitter(submitter, this);
+          submitter.click();
+        } else {
+          submitter = document.createElement("input");
+          submitter.type = "submit";
+          submitter.hidden = true;
+          this.appendChild(submitter);
+          submitter.click();
+          this.removeChild(submitter);
+        }
+      };
+
+      function validateSubmitter(submitter, form) {
+        submitter instanceof HTMLElement || raise(TypeError, "parameter 1 is not of type 'HTMLElement'");
+        submitter.type == "submit" || raise(TypeError, "The specified element is not a submit button");
+        submitter.form == form || raise(DOMException, "The specified element is not owned by this form element", "NotFoundError");
+      }
+
+      function raise(errorConstructor, message, name) {
+        throw new errorConstructor("Failed to execute 'requestSubmit' on 'HTMLFormElement': " + message + ".", name)
+      }
+    })(HTMLFormElement.prototype);
+
+    const submittersByForm = new WeakMap;
+    function findSubmitterFromClickTarget(target) {
+        const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
+        const candidate = element ? element.closest("input, button") : null;
+        return (candidate === null || candidate === void 0 ? void 0 : candidate.type) == "submit" ? candidate : null;
+    }
+    function clickCaptured(event) {
+        const submitter = findSubmitterFromClickTarget(event.target);
+        if (submitter && submitter.form) {
+            submittersByForm.set(submitter.form, submitter);
+        }
+    }
+    (function () {
+        if ("submitter" in Event.prototype)
+            return;
+        let prototype;
+        if ("SubmitEvent" in window && /Apple Computer/.test(navigator.vendor)) {
+            prototype = window.SubmitEvent.prototype;
+        }
+        else if ("SubmitEvent" in window) {
+            return;
+        }
+        else {
+            prototype = window.Event.prototype;
+        }
+        addEventListener("click", clickCaptured, true);
+        Object.defineProperty(prototype, "submitter", {
+            get() {
+                if (this.type == "submit" && this.target instanceof HTMLFormElement) {
+                    return submittersByForm.get(this.target);
+                }
+            }
+        });
+    })();
+
+    var FrameLoadingStyle;
+    (function (FrameLoadingStyle) {
+        FrameLoadingStyle["eager"] = "eager";
+        FrameLoadingStyle["lazy"] = "lazy";
+    })(FrameLoadingStyle || (FrameLoadingStyle = {}));
+    class FrameElement extends HTMLElement {
+        constructor() {
+            super();
+            this.loaded = Promise.resolve();
+            this.delegate = new FrameElement.delegateConstructor(this);
+        }
+        static get observedAttributes() {
+            return ["disabled", "loading", "src"];
+        }
+        connectedCallback() {
+            this.delegate.connect();
+        }
+        disconnectedCallback() {
+            this.delegate.disconnect();
+        }
+        reload() {
+            const { src } = this;
+            this.src = null;
+            this.src = src;
+        }
+        attributeChangedCallback(name) {
+            if (name == "loading") {
+                this.delegate.loadingStyleChanged();
+            }
+            else if (name == "src") {
+                this.delegate.sourceURLChanged();
+            }
+            else {
+                this.delegate.disabledChanged();
+            }
+        }
+        get src() {
+            return this.getAttribute("src");
+        }
+        set src(value) {
+            if (value) {
+                this.setAttribute("src", value);
+            }
+            else {
+                this.removeAttribute("src");
+            }
+        }
+        get loading() {
+            return frameLoadingStyleFromString(this.getAttribute("loading") || "");
+        }
+        set loading(value) {
+            if (value) {
+                this.setAttribute("loading", value);
+            }
+            else {
+                this.removeAttribute("loading");
+            }
+        }
+        get disabled() {
+            return this.hasAttribute("disabled");
+        }
+        set disabled(value) {
+            if (value) {
+                this.setAttribute("disabled", "");
+            }
+            else {
+                this.removeAttribute("disabled");
+            }
+        }
+        get autoscroll() {
+            return this.hasAttribute("autoscroll");
+        }
+        set autoscroll(value) {
+            if (value) {
+                this.setAttribute("autoscroll", "");
+            }
+            else {
+                this.removeAttribute("autoscroll");
+            }
+        }
+        get complete() {
+            return !this.delegate.isLoading;
+        }
+        get isActive() {
+            return this.ownerDocument === document && !this.isPreview;
+        }
+        get isPreview() {
+            var _a, _b;
+            return (_b = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.documentElement) === null || _b === void 0 ? void 0 : _b.hasAttribute("data-turbo-preview");
+        }
+    }
+    function frameLoadingStyleFromString(style) {
+        switch (style.toLowerCase()) {
+            case "lazy": return FrameLoadingStyle.lazy;
+            default: return FrameLoadingStyle.eager;
+        }
+    }
+
+    function expandURL(locatable) {
+        return new URL(locatable.toString(), document.baseURI);
+    }
+    function getAnchor(url) {
+        let anchorMatch;
+        if (url.hash) {
+            return url.hash.slice(1);
+        }
+        else if (anchorMatch = url.href.match(/#(.*)$/)) {
+            return anchorMatch[1];
+        }
+    }
+    function getAction(form, submitter) {
+        const action = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formaction")) || form.getAttribute("action") || form.action;
+        return expandURL(action);
+    }
+    function getExtension(url) {
+        return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
+    }
+    function isHTML(url) {
+        return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml))$/);
+    }
+    function isPrefixedBy(baseURL, url) {
+        const prefix = getPrefix(url);
+        return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
+    }
+    function locationIsVisitable(location, rootLocation) {
+        return isPrefixedBy(location, rootLocation) && isHTML(location);
+    }
+    function getRequestURL(url) {
+        const anchor = getAnchor(url);
+        return anchor != null
+            ? url.href.slice(0, -(anchor.length + 1))
+            : url.href;
+    }
+    function toCacheKey(url) {
+        return getRequestURL(url);
+    }
+    function urlsAreEqual(left, right) {
+        return expandURL(left).href == expandURL(right).href;
+    }
+    function getPathComponents(url) {
+        return url.pathname.split("/").slice(1);
+    }
+    function getLastPathComponent(url) {
+        return getPathComponents(url).slice(-1)[0];
+    }
+    function getPrefix(url) {
+        return addTrailingSlash(url.origin + url.pathname);
+    }
+    function addTrailingSlash(value) {
+        return value.endsWith("/") ? value : value + "/";
+    }
+
+    class FetchResponse {
+        constructor(response) {
+            this.response = response;
+        }
+        get succeeded() {
+            return this.response.ok;
+        }
+        get failed() {
+            return !this.succeeded;
+        }
+        get clientError() {
+            return this.statusCode >= 400 && this.statusCode <= 499;
+        }
+        get serverError() {
+            return this.statusCode >= 500 && this.statusCode <= 599;
+        }
+        get redirected() {
+            return this.response.redirected;
+        }
+        get location() {
+            return expandURL(this.response.url);
+        }
+        get isHTML() {
+            return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/);
+        }
+        get statusCode() {
+            return this.response.status;
+        }
+        get contentType() {
+            return this.header("Content-Type");
+        }
+        get responseText() {
+            return this.response.clone().text();
+        }
+        get responseHTML() {
+            if (this.isHTML) {
+                return this.response.clone().text();
+            }
+            else {
+                return Promise.resolve(undefined);
+            }
+        }
+        header(name) {
+            return this.response.headers.get(name);
+        }
+    }
+
+    function dispatch(eventName, { target, cancelable, detail } = {}) {
+        const event = new CustomEvent(eventName, { cancelable, bubbles: true, detail });
+        if (target && target.isConnected) {
+            target.dispatchEvent(event);
+        }
+        else {
+            document.documentElement.dispatchEvent(event);
+        }
+        return event;
+    }
+    function nextAnimationFrame() {
+        return new Promise(resolve => requestAnimationFrame(() => resolve()));
+    }
+    function nextEventLoopTick() {
+        return new Promise(resolve => setTimeout(() => resolve(), 0));
+    }
+    function nextMicrotask() {
+        return Promise.resolve();
+    }
+    function parseHTMLDocument(html = "") {
+        return new DOMParser().parseFromString(html, "text/html");
+    }
+    function unindent(strings, ...values) {
+        const lines = interpolate(strings, values).replace(/^\n/, "").split("\n");
+        const match = lines[0].match(/^\s+/);
+        const indent = match ? match[0].length : 0;
+        return lines.map(line => line.slice(indent)).join("\n");
+    }
+    function interpolate(strings, values) {
+        return strings.reduce((result, string, i) => {
+            const value = values[i] == undefined ? "" : values[i];
+            return result + string + value;
+        }, "");
+    }
+    function uuid() {
+        return Array.apply(null, { length: 36 }).map((_, i) => {
+            if (i == 8 || i == 13 || i == 18 || i == 23) {
+                return "-";
+            }
+            else if (i == 14) {
+                return "4";
+            }
+            else if (i == 19) {
+                return (Math.floor(Math.random() * 4) + 8).toString(16);
+            }
+            else {
+                return Math.floor(Math.random() * 15).toString(16);
+            }
+        }).join("");
+    }
+    function getAttribute(attributeName, ...elements) {
+        for (const value of elements.map(element => element === null || element === void 0 ? void 0 : element.getAttribute(attributeName))) {
+            if (typeof value == "string")
+                return value;
+        }
+        return null;
+    }
+    function markAsBusy(...elements) {
+        for (const element of elements) {
+            if (element.localName == "turbo-frame") {
+                element.setAttribute("busy", "");
+            }
+            element.setAttribute("aria-busy", "true");
+        }
+    }
+    function clearBusyState(...elements) {
+        for (const element of elements) {
+            if (element.localName == "turbo-frame") {
+                element.removeAttribute("busy");
+            }
+            element.removeAttribute("aria-busy");
+        }
+    }
+
+    var FetchMethod;
+    (function (FetchMethod) {
+        FetchMethod[FetchMethod["get"] = 0] = "get";
+        FetchMethod[FetchMethod["post"] = 1] = "post";
+        FetchMethod[FetchMethod["put"] = 2] = "put";
+        FetchMethod[FetchMethod["patch"] = 3] = "patch";
+        FetchMethod[FetchMethod["delete"] = 4] = "delete";
+    })(FetchMethod || (FetchMethod = {}));
+    function fetchMethodFromString(method) {
+        switch (method.toLowerCase()) {
+            case "get": return FetchMethod.get;
+            case "post": return FetchMethod.post;
+            case "put": return FetchMethod.put;
+            case "patch": return FetchMethod.patch;
+            case "delete": return FetchMethod.delete;
+        }
+    }
+    class FetchRequest {
+        constructor(delegate, method, location, body = new URLSearchParams, target = null) {
+            this.abortController = new AbortController;
+            this.resolveRequestPromise = (value) => { };
+            this.delegate = delegate;
+            this.method = method;
+            this.headers = this.defaultHeaders;
+            this.body = body;
+            this.url = location;
+            this.target = target;
+        }
+        get location() {
+            return this.url;
+        }
+        get params() {
+            return this.url.searchParams;
+        }
+        get entries() {
+            return this.body ? Array.from(this.body.entries()) : [];
+        }
+        cancel() {
+            this.abortController.abort();
+        }
+        async perform() {
+            var _a, _b;
+            const { fetchOptions } = this;
+            (_b = (_a = this.delegate).prepareHeadersForRequest) === null || _b === void 0 ? void 0 : _b.call(_a, this.headers, this);
+            await this.allowRequestToBeIntercepted(fetchOptions);
+            try {
+                this.delegate.requestStarted(this);
+                const response = await fetch(this.url.href, fetchOptions);
+                return await this.receive(response);
+            }
+            catch (error) {
+                if (error.name !== 'AbortError') {
+                    this.delegate.requestErrored(this, error);
+                    throw error;
+                }
+            }
+            finally {
+                this.delegate.requestFinished(this);
+            }
+        }
+        async receive(response) {
+            const fetchResponse = new FetchResponse(response);
+            const event = dispatch("turbo:before-fetch-response", { cancelable: true, detail: { fetchResponse }, target: this.target });
+            if (event.defaultPrevented) {
+                this.delegate.requestPreventedHandlingResponse(this, fetchResponse);
+            }
+            else if (fetchResponse.succeeded) {
+                this.delegate.requestSucceededWithResponse(this, fetchResponse);
+            }
+            else {
+                this.delegate.requestFailedWithResponse(this, fetchResponse);
+            }
+            return fetchResponse;
+        }
+        get fetchOptions() {
+            var _a;
+            return {
+                method: FetchMethod[this.method].toUpperCase(),
+                credentials: "same-origin",
+                headers: this.headers,
+                redirect: "follow",
+                body: this.isIdempotent ? null : this.body,
+                signal: this.abortSignal,
+                referrer: (_a = this.delegate.referrer) === null || _a === void 0 ? void 0 : _a.href
+            };
+        }
+        get defaultHeaders() {
+            return {
+                "Accept": "text/html, application/xhtml+xml"
+            };
+        }
+        get isIdempotent() {
+            return this.method == FetchMethod.get;
+        }
+        get abortSignal() {
+            return this.abortController.signal;
+        }
+        async allowRequestToBeIntercepted(fetchOptions) {
+            const requestInterception = new Promise(resolve => this.resolveRequestPromise = resolve);
+            const event = dispatch("turbo:before-fetch-request", {
+                cancelable: true,
+                detail: {
+                    fetchOptions,
+                    url: this.url,
+                    resume: this.resolveRequestPromise
+                },
+                target: this.target
+            });
+            if (event.defaultPrevented)
+                await requestInterception;
+        }
+    }
+
+    class AppearanceObserver {
+        constructor(delegate, element) {
+            this.started = false;
+            this.intersect = entries => {
+                const lastEntry = entries.slice(-1)[0];
+                if (lastEntry === null || lastEntry === void 0 ? void 0 : lastEntry.isIntersecting) {
+                    this.delegate.elementAppearedInViewport(this.element);
+                }
+            };
+            this.delegate = delegate;
+            this.element = element;
+            this.intersectionObserver = new IntersectionObserver(this.intersect);
+        }
+        start() {
+            if (!this.started) {
+                this.started = true;
+                this.intersectionObserver.observe(this.element);
+            }
+        }
+        stop() {
+            if (this.started) {
+                this.started = false;
+                this.intersectionObserver.unobserve(this.element);
+            }
+        }
+    }
+
+    class StreamMessage {
+        constructor(html) {
+            this.templateElement = document.createElement("template");
+            this.templateElement.innerHTML = html;
+        }
+        static wrap(message) {
+            if (typeof message == "string") {
+                return new this(message);
+            }
+            else {
+                return message;
+            }
+        }
+        get fragment() {
+            const fragment = document.createDocumentFragment();
+            for (const element of this.foreignElements) {
+                fragment.appendChild(document.importNode(element, true));
+            }
+            return fragment;
+        }
+        get foreignElements() {
+            return this.templateChildren.reduce((streamElements, child) => {
+                if (child.tagName.toLowerCase() == "turbo-stream") {
+                    return [...streamElements, child];
+                }
+                else {
+                    return streamElements;
+                }
+            }, []);
+        }
+        get templateChildren() {
+            return Array.from(this.templateElement.content.children);
+        }
+    }
+    StreamMessage.contentType = "text/vnd.turbo-stream.html";
+
+    var FormSubmissionState;
+    (function (FormSubmissionState) {
+        FormSubmissionState[FormSubmissionState["initialized"] = 0] = "initialized";
+        FormSubmissionState[FormSubmissionState["requesting"] = 1] = "requesting";
+        FormSubmissionState[FormSubmissionState["waiting"] = 2] = "waiting";
+        FormSubmissionState[FormSubmissionState["receiving"] = 3] = "receiving";
+        FormSubmissionState[FormSubmissionState["stopping"] = 4] = "stopping";
+        FormSubmissionState[FormSubmissionState["stopped"] = 5] = "stopped";
+    })(FormSubmissionState || (FormSubmissionState = {}));
+    var FormEnctype;
+    (function (FormEnctype) {
+        FormEnctype["urlEncoded"] = "application/x-www-form-urlencoded";
+        FormEnctype["multipart"] = "multipart/form-data";
+        FormEnctype["plain"] = "text/plain";
+    })(FormEnctype || (FormEnctype = {}));
+    function formEnctypeFromString(encoding) {
+        switch (encoding.toLowerCase()) {
+            case FormEnctype.multipart: return FormEnctype.multipart;
+            case FormEnctype.plain: return FormEnctype.plain;
+            default: return FormEnctype.urlEncoded;
+        }
+    }
+    class FormSubmission {
+        constructor(delegate, formElement, submitter, mustRedirect = false) {
+            this.state = FormSubmissionState.initialized;
+            this.delegate = delegate;
+            this.formElement = formElement;
+            this.submitter = submitter;
+            this.formData = buildFormData(formElement, submitter);
+            this.location = expandURL(this.action);
+            if (this.method == FetchMethod.get) {
+                mergeFormDataEntries(this.location, [...this.body.entries()]);
+            }
+            this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body, this.formElement);
+            this.mustRedirect = mustRedirect;
+        }
+        static confirmMethod(message, element) {
+            return confirm(message);
+        }
+        get method() {
+            var _a;
+            const method = ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formmethod")) || this.formElement.getAttribute("method") || "";
+            return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get;
+        }
+        get action() {
+            var _a;
+            const formElementAction = typeof this.formElement.action === 'string' ? this.formElement.action : null;
+            return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formaction")) || this.formElement.getAttribute("action") || formElementAction || "";
+        }
+        get body() {
+            if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
+                return new URLSearchParams(this.stringFormData);
+            }
+            else {
+                return this.formData;
+            }
+        }
+        get enctype() {
+            var _a;
+            return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
+        }
+        get isIdempotent() {
+            return this.fetchRequest.isIdempotent;
+        }
+        get stringFormData() {
+            return [...this.formData].reduce((entries, [name, value]) => {
+                return entries.concat(typeof value == "string" ? [[name, value]] : []);
+            }, []);
+        }
+        get confirmationMessage() {
+            return this.formElement.getAttribute("data-turbo-confirm");
+        }
+        get needsConfirmation() {
+            return this.confirmationMessage !== null;
+        }
+        async start() {
+            const { initialized, requesting } = FormSubmissionState;
+            if (this.needsConfirmation) {
+                const answer = FormSubmission.confirmMethod(this.confirmationMessage, this.formElement);
+                if (!answer) {
+                    return;
+                }
+            }
+            if (this.state == initialized) {
+                this.state = requesting;
+                return this.fetchRequest.perform();
+            }
+        }
+        stop() {
+            const { stopping, stopped } = FormSubmissionState;
+            if (this.state != stopping && this.state != stopped) {
+                this.state = stopping;
+                this.fetchRequest.cancel();
+                return true;
+            }
+        }
+        prepareHeadersForRequest(headers, request) {
+            if (!request.isIdempotent) {
+                const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
+                if (token) {
+                    headers["X-CSRF-Token"] = token;
+                }
+                headers["Accept"] = [StreamMessage.contentType, headers["Accept"]].join(", ");
+            }
+        }
+        requestStarted(request) {
+            var _a;
+            this.state = FormSubmissionState.waiting;
+            (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.setAttribute("disabled", "");
+            dispatch("turbo:submit-start", { target: this.formElement, detail: { formSubmission: this } });
+            this.delegate.formSubmissionStarted(this);
+        }
+        requestPreventedHandlingResponse(request, response) {
+            this.result = { success: response.succeeded, fetchResponse: response };
+        }
+        requestSucceededWithResponse(request, response) {
+            if (response.clientError || response.serverError) {
+                this.delegate.formSubmissionFailedWithResponse(this, response);
+            }
+            else if (this.requestMustRedirect(request) && responseSucceededWithoutRedirect(response)) {
+                const error = new Error("Form responses must redirect to another location");
+                this.delegate.formSubmissionErrored(this, error);
+            }
+            else {
+                this.state = FormSubmissionState.receiving;
+                this.result = { success: true, fetchResponse: response };
+                this.delegate.formSubmissionSucceededWithResponse(this, response);
+            }
+        }
+        requestFailedWithResponse(request, response) {
+            this.result = { success: false, fetchResponse: response };
+            this.delegate.formSubmissionFailedWithResponse(this, response);
+        }
+        requestErrored(request, error) {
+            this.result = { success: false, error };
+            this.delegate.formSubmissionErrored(this, error);
+        }
+        requestFinished(request) {
+            var _a;
+            this.state = FormSubmissionState.stopped;
+            (_a = this.submitter) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled");
+            dispatch("turbo:submit-end", { target: this.formElement, detail: Object.assign({ formSubmission: this }, this.result) });
+            this.delegate.formSubmissionFinished(this);
+        }
+        requestMustRedirect(request) {
+            return !request.isIdempotent && this.mustRedirect;
+        }
+    }
+    function buildFormData(formElement, submitter) {
+        const formData = new FormData(formElement);
+        const name = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("name");
+        const value = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("value");
+        if (name && value != null && formData.get(name) != value) {
+            formData.append(name, value);
+        }
+        return formData;
+    }
+    function getCookieValue(cookieName) {
+        if (cookieName != null) {
+            const cookies = document.cookie ? document.cookie.split("; ") : [];
+            const cookie = cookies.find((cookie) => cookie.startsWith(cookieName));
+            if (cookie) {
+                const value = cookie.split("=").slice(1).join("=");
+                return value ? decodeURIComponent(value) : undefined;
+            }
+        }
+    }
+    function getMetaContent(name) {
+        const element = document.querySelector(`meta[name="${name}"]`);
+        return element && element.content;
+    }
+    function responseSucceededWithoutRedirect(response) {
+        return response.statusCode == 200 && !response.redirected;
+    }
+    function mergeFormDataEntries(url, entries) {
+        const searchParams = new URLSearchParams;
+        for (const [name, value] of entries) {
+            if (value instanceof File)
+                continue;
+            searchParams.append(name, value);
+        }
+        url.search = searchParams.toString();
+        return url;
+    }
+
+    class Snapshot {
+        constructor(element) {
+            this.element = element;
+        }
+        get children() {
+            return [...this.element.children];
+        }
+        hasAnchor(anchor) {
+            return this.getElementForAnchor(anchor) != null;
+        }
+        getElementForAnchor(anchor) {
+            return anchor ? this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`) : null;
+        }
+        get isConnected() {
+            return this.element.isConnected;
+        }
+        get firstAutofocusableElement() {
+            return this.element.querySelector("[autofocus]");
+        }
+        get permanentElements() {
+            return [...this.element.querySelectorAll("[id][data-turbo-permanent]")];
+        }
+        getPermanentElementById(id) {
+            return this.element.querySelector(`#${id}[data-turbo-permanent]`);
+        }
+        getPermanentElementMapForSnapshot(snapshot) {
+            const permanentElementMap = {};
+            for (const currentPermanentElement of this.permanentElements) {
+                const { id } = currentPermanentElement;
+                const newPermanentElement = snapshot.getPermanentElementById(id);
+                if (newPermanentElement) {
+                    permanentElementMap[id] = [currentPermanentElement, newPermanentElement];
+                }
+            }
+            return permanentElementMap;
+        }
+    }
+
+    class FormInterceptor {
+        constructor(delegate, element) {
+            this.submitBubbled = ((event) => {
+                const form = event.target;
+                if (!event.defaultPrevented && form instanceof HTMLFormElement && form.closest("turbo-frame, html") == this.element) {
+                    const submitter = event.submitter || undefined;
+                    const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.method;
+                    if (method != "dialog" && this.delegate.shouldInterceptFormSubmission(form, submitter)) {
+                        event.preventDefault();
+                        event.stopImmediatePropagation();
+                        this.delegate.formSubmissionIntercepted(form, submitter);
+                    }
+                }
+            });
+            this.delegate = delegate;
+            this.element = element;
+        }
+        start() {
+            this.element.addEventListener("submit", this.submitBubbled);
+        }
+        stop() {
+            this.element.removeEventListener("submit", this.submitBubbled);
+        }
+    }
+
+    class View {
+        constructor(delegate, element) {
+            this.resolveRenderPromise = (value) => { };
+            this.resolveInterceptionPromise = (value) => { };
+            this.delegate = delegate;
+            this.element = element;
+        }
+        scrollToAnchor(anchor) {
+            const element = this.snapshot.getElementForAnchor(anchor);
+            if (element) {
+                this.scrollToElement(element);
+                this.focusElement(element);
+            }
+            else {
+                this.scrollToPosition({ x: 0, y: 0 });
+            }
+        }
+        scrollToAnchorFromLocation(location) {
+            this.scrollToAnchor(getAnchor(location));
+        }
+        scrollToElement(element) {
+            element.scrollIntoView();
+        }
+        focusElement(element) {
+            if (element instanceof HTMLElement) {
+                if (element.hasAttribute("tabindex")) {
+                    element.focus();
+                }
+                else {
+                    element.setAttribute("tabindex", "-1");
+                    element.focus();
+                    element.removeAttribute("tabindex");
+                }
+            }
+        }
+        scrollToPosition({ x, y }) {
+            this.scrollRoot.scrollTo(x, y);
+        }
+        scrollToTop() {
+            this.scrollToPosition({ x: 0, y: 0 });
+        }
+        get scrollRoot() {
+            return window;
+        }
+        async render(renderer) {
+            const { isPreview, shouldRender, newSnapshot: snapshot } = renderer;
+            if (shouldRender) {
+                try {
+                    this.renderPromise = new Promise(resolve => this.resolveRenderPromise = resolve);
+                    this.renderer = renderer;
+                    this.prepareToRenderSnapshot(renderer);
+                    const renderInterception = new Promise(resolve => this.resolveInterceptionPromise = resolve);
+                    const immediateRender = this.delegate.allowsImmediateRender(snapshot, this.resolveInterceptionPromise);
+                    if (!immediateRender)
+                        await renderInterception;
+                    await this.renderSnapshot(renderer);
+                    this.delegate.viewRenderedSnapshot(snapshot, isPreview);
+                    this.finishRenderingSnapshot(renderer);
+                }
+                finally {
+                    delete this.renderer;
+                    this.resolveRenderPromise(undefined);
+                    delete this.renderPromise;
+                }
+            }
+            else {
+                this.invalidate();
+            }
+        }
+        invalidate() {
+            this.delegate.viewInvalidated();
+        }
+        prepareToRenderSnapshot(renderer) {
+            this.markAsPreview(renderer.isPreview);
+            renderer.prepareToRender();
+        }
+        markAsPreview(isPreview) {
+            if (isPreview) {
+                this.element.setAttribute("data-turbo-preview", "");
+            }
+            else {
+                this.element.removeAttribute("data-turbo-preview");
+            }
+        }
+        async renderSnapshot(renderer) {
+            await renderer.render();
+        }
+        finishRenderingSnapshot(renderer) {
+            renderer.finishRendering();
+        }
+    }
+
+    class FrameView extends View {
+        invalidate() {
+            this.element.innerHTML = "";
+        }
+        get snapshot() {
+            return new Snapshot(this.element);
+        }
+    }
+
+    class LinkInterceptor {
+        constructor(delegate, element) {
+            this.clickBubbled = (event) => {
+                if (this.respondsToEventTarget(event.target)) {
+                    this.clickEvent = event;
+                }
+                else {
+                    delete this.clickEvent;
+                }
+            };
+            this.linkClicked = ((event) => {
+                if (this.clickEvent && this.respondsToEventTarget(event.target) && event.target instanceof Element) {
+                    if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url)) {
+                        this.clickEvent.preventDefault();
+                        event.preventDefault();
+                        this.delegate.linkClickIntercepted(event.target, event.detail.url);
+                    }
+                }
+                delete this.clickEvent;
+            });
+            this.willVisit = () => {
+                delete this.clickEvent;
+            };
+            this.delegate = delegate;
+            this.element = element;
+        }
+        start() {
+            this.element.addEventListener("click", this.clickBubbled);
+            document.addEventListener("turbo:click", this.linkClicked);
+            document.addEventListener("turbo:before-visit", this.willVisit);
+        }
+        stop() {
+            this.element.removeEventListener("click", this.clickBubbled);
+            document.removeEventListener("turbo:click", this.linkClicked);
+            document.removeEventListener("turbo:before-visit", this.willVisit);
+        }
+        respondsToEventTarget(target) {
+            const element = target instanceof Element
+                ? target
+                : target instanceof Node
+                    ? target.parentElement
+                    : null;
+            return element && element.closest("turbo-frame, html") == this.element;
+        }
+    }
+
+    class Bardo {
+        constructor(permanentElementMap) {
+            this.permanentElementMap = permanentElementMap;
+        }
+        static preservingPermanentElements(permanentElementMap, callback) {
+            const bardo = new this(permanentElementMap);
+            bardo.enter();
+            callback();
+            bardo.leave();
+        }
+        enter() {
+            for (const id in this.permanentElementMap) {
+                const [, newPermanentElement] = this.permanentElementMap[id];
+                this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);
+            }
+        }
+        leave() {
+            for (const id in this.permanentElementMap) {
+                const [currentPermanentElement] = this.permanentElementMap[id];
+                this.replaceCurrentPermanentElementWithClone(currentPermanentElement);
+                this.replacePlaceholderWithPermanentElement(currentPermanentElement);
+            }
+        }
+        replaceNewPermanentElementWithPlaceholder(permanentElement) {
+            const placeholder = createPlaceholderForPermanentElement(permanentElement);
+            permanentElement.replaceWith(placeholder);
+        }
+        replaceCurrentPermanentElementWithClone(permanentElement) {
+            const clone = permanentElement.cloneNode(true);
+            permanentElement.replaceWith(clone);
+        }
+        replacePlaceholderWithPermanentElement(permanentElement) {
+            const placeholder = this.getPlaceholderById(permanentElement.id);
+            placeholder === null || placeholder === void 0 ? void 0 : placeholder.replaceWith(permanentElement);
+        }
+        getPlaceholderById(id) {
+            return this.placeholders.find(element => element.content == id);
+        }
+        get placeholders() {
+            return [...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]")];
+        }
+    }
+    function createPlaceholderForPermanentElement(permanentElement) {
+        const element = document.createElement("meta");
+        element.setAttribute("name", "turbo-permanent-placeholder");
+        element.setAttribute("content", permanentElement.id);
+        return element;
+    }
+
+    class Renderer {
+        constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
+            this.currentSnapshot = currentSnapshot;
+            this.newSnapshot = newSnapshot;
+            this.isPreview = isPreview;
+            this.willRender = willRender;
+            this.promise = new Promise((resolve, reject) => this.resolvingFunctions = { resolve, reject });
+        }
+        get shouldRender() {
+            return true;
+        }
+        prepareToRender() {
+            return;
+        }
+        finishRendering() {
+            if (this.resolvingFunctions) {
+                this.resolvingFunctions.resolve();
+                delete this.resolvingFunctions;
+            }
+        }
+        createScriptElement(element) {
+            if (element.getAttribute("data-turbo-eval") == "false") {
+                return element;
+            }
+            else {
+                const createdScriptElement = document.createElement("script");
+                if (this.cspNonce) {
+                    createdScriptElement.nonce = this.cspNonce;
+                }
+                createdScriptElement.textContent = element.textContent;
+                createdScriptElement.async = false;
+                copyElementAttributes(createdScriptElement, element);
+                return createdScriptElement;
+            }
+        }
+        preservingPermanentElements(callback) {
+            Bardo.preservingPermanentElements(this.permanentElementMap, callback);
+        }
+        focusFirstAutofocusableElement() {
+            const element = this.connectedSnapshot.firstAutofocusableElement;
+            if (elementIsFocusable(element)) {
+                element.focus();
+            }
+        }
+        get connectedSnapshot() {
+            return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot;
+        }
+        get currentElement() {
+            return this.currentSnapshot.element;
+        }
+        get newElement() {
+            return this.newSnapshot.element;
+        }
+        get permanentElementMap() {
+            return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot);
+        }
+        get cspNonce() {
+            var _a;
+            return (_a = document.head.querySelector('meta[name="csp-nonce"]')) === null || _a === void 0 ? void 0 : _a.getAttribute("content");
+        }
+    }
+    function copyElementAttributes(destinationElement, sourceElement) {
+        for (const { name, value } of [...sourceElement.attributes]) {
+            destinationElement.setAttribute(name, value);
+        }
+    }
+    function elementIsFocusable(element) {
+        return element && typeof element.focus == "function";
+    }
+
+    class FrameRenderer extends Renderer {
+        get shouldRender() {
+            return true;
+        }
+        async render() {
+            await nextAnimationFrame();
+            this.preservingPermanentElements(() => {
+                this.loadFrameElement();
+            });
+            this.scrollFrameIntoView();
+            await nextAnimationFrame();
+            this.focusFirstAutofocusableElement();
+            await nextAnimationFrame();
+            this.activateScriptElements();
+        }
+        loadFrameElement() {
+            var _a;
+            const destinationRange = document.createRange();
+            destinationRange.selectNodeContents(this.currentElement);
+            destinationRange.deleteContents();
+            const frameElement = this.newElement;
+            const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
+            if (sourceRange) {
+                sourceRange.selectNodeContents(frameElement);
+                this.currentElement.appendChild(sourceRange.extractContents());
+            }
+        }
+        scrollFrameIntoView() {
+            if (this.currentElement.autoscroll || this.newElement.autoscroll) {
+                const element = this.currentElement.firstElementChild;
+                const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
+                if (element) {
+                    element.scrollIntoView({ block });
+                    return true;
+                }
+            }
+            return false;
+        }
+        activateScriptElements() {
+            for (const inertScriptElement of this.newScriptElements) {
+                const activatedScriptElement = this.createScriptElement(inertScriptElement);
+                inertScriptElement.replaceWith(activatedScriptElement);
+            }
+        }
+        get newScriptElements() {
+            return this.currentElement.querySelectorAll("script");
+        }
+    }
+    function readScrollLogicalPosition(value, defaultValue) {
+        if (value == "end" || value == "start" || value == "center" || value == "nearest") {
+            return value;
+        }
+        else {
+            return defaultValue;
+        }
+    }
+
+    class ProgressBar {
+        constructor() {
+            this.hiding = false;
+            this.value = 0;
+            this.visible = false;
+            this.trickle = () => {
+                this.setValue(this.value + Math.random() / 100);
+            };
+            this.stylesheetElement = this.createStylesheetElement();
+            this.progressElement = this.createProgressElement();
+            this.installStylesheetElement();
+            this.setValue(0);
+        }
+        static get defaultCSS() {
+            return unindent `
+      .turbo-progress-bar {
+        position: fixed;
+        display: block;
+        top: 0;
+        left: 0;
+        height: 3px;
+        background: #0076ff;
+        z-index: 9999;
+        transition:
+          width ${ProgressBar.animationDuration}ms ease-out,
+          opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;
+        transform: translate3d(0, 0, 0);
+      }
+    `;
+        }
+        show() {
+            if (!this.visible) {
+                this.visible = true;
+                this.installProgressElement();
+                this.startTrickling();
+            }
+        }
+        hide() {
+            if (this.visible && !this.hiding) {
+                this.hiding = true;
+                this.fadeProgressElement(() => {
+                    this.uninstallProgressElement();
+                    this.stopTrickling();
+                    this.visible = false;
+                    this.hiding = false;
+                });
+            }
+        }
+        setValue(value) {
+            this.value = value;
+            this.refresh();
+        }
+        installStylesheetElement() {
+            document.head.insertBefore(this.stylesheetElement, document.head.firstChild);
+        }
+        installProgressElement() {
+            this.progressElement.style.width = "0";
+            this.progressElement.style.opacity = "1";
+            document.documentElement.insertBefore(this.progressElement, document.body);
+            this.refresh();
+        }
+        fadeProgressElement(callback) {
+            this.progressElement.style.opacity = "0";
+            setTimeout(callback, ProgressBar.animationDuration * 1.5);
+        }
+        uninstallProgressElement() {
+            if (this.progressElement.parentNode) {
+                document.documentElement.removeChild(this.progressElement);
+            }
+        }
+        startTrickling() {
+            if (!this.trickleInterval) {
+                this.trickleInterval = window.setInterval(this.trickle, ProgressBar.animationDuration);
+            }
+        }
+        stopTrickling() {
+            window.clearInterval(this.trickleInterval);
+            delete this.trickleInterval;
+        }
+        refresh() {
+            requestAnimationFrame(() => {
+                this.progressElement.style.width = `${10 + (this.value * 90)}%`;
+            });
+        }
+        createStylesheetElement() {
+            const element = document.createElement("style");
+            element.type = "text/css";
+            element.textContent = ProgressBar.defaultCSS;
+            return element;
+        }
+        createProgressElement() {
+            const element = document.createElement("div");
+            element.className = "turbo-progress-bar";
+            return element;
+        }
+    }
+    ProgressBar.animationDuration = 300;
+
+    class HeadSnapshot extends Snapshot {
+        constructor() {
+            super(...arguments);
+            this.detailsByOuterHTML = this.children
+                .filter((element) => !elementIsNoscript(element))
+                .map((element) => elementWithoutNonce(element))
+                .reduce((result, element) => {
+                const { outerHTML } = element;
+                const details = outerHTML in result
+                    ? result[outerHTML]
+                    : {
+                        type: elementType(element),
+                        tracked: elementIsTracked(element),
+                        elements: []
+                    };
+                return Object.assign(Object.assign({}, result), { [outerHTML]: Object.assign(Object.assign({}, details), { elements: [...details.elements, element] }) });
+            }, {});
+        }
+        get trackedElementSignature() {
+            return Object.keys(this.detailsByOuterHTML)
+                .filter(outerHTML => this.detailsByOuterHTML[outerHTML].tracked)
+                .join("");
+        }
+        getScriptElementsNotInSnapshot(snapshot) {
+            return this.getElementsMatchingTypeNotInSnapshot("script", snapshot);
+        }
+        getStylesheetElementsNotInSnapshot(snapshot) {
+            return this.getElementsMatchingTypeNotInSnapshot("stylesheet", snapshot);
+        }
+        getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
+            return Object.keys(this.detailsByOuterHTML)
+                .filter(outerHTML => !(outerHTML in snapshot.detailsByOuterHTML))
+                .map(outerHTML => this.detailsByOuterHTML[outerHTML])
+                .filter(({ type }) => type == matchedType)
+                .map(({ elements: [element] }) => element);
+        }
+        get provisionalElements() {
+            return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {
+                const { type, tracked, elements } = this.detailsByOuterHTML[outerHTML];
+                if (type == null && !tracked) {
+                    return [...result, ...elements];
+                }
+                else if (elements.length > 1) {
+                    return [...result, ...elements.slice(1)];
+                }
+                else {
+                    return result;
+                }
+            }, []);
+        }
+        getMetaValue(name) {
+            const element = this.findMetaElementByName(name);
+            return element
+                ? element.getAttribute("content")
+                : null;
+        }
+        findMetaElementByName(name) {
+            return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {
+                const { elements: [element] } = this.detailsByOuterHTML[outerHTML];
+                return elementIsMetaElementWithName(element, name) ? element : result;
+            }, undefined);
+        }
+    }
+    function elementType(element) {
+        if (elementIsScript(element)) {
+            return "script";
+        }
+        else if (elementIsStylesheet(element)) {
+            return "stylesheet";
+        }
+    }
+    function elementIsTracked(element) {
+        return element.getAttribute("data-turbo-track") == "reload";
+    }
+    function elementIsScript(element) {
+        const tagName = element.tagName.toLowerCase();
+        return tagName == "script";
+    }
+    function elementIsNoscript(element) {
+        const tagName = element.tagName.toLowerCase();
+        return tagName == "noscript";
+    }
+    function elementIsStylesheet(element) {
+        const tagName = element.tagName.toLowerCase();
+        return tagName == "style" || (tagName == "link" && element.getAttribute("rel") == "stylesheet");
+    }
+    function elementIsMetaElementWithName(element, name) {
+        const tagName = element.tagName.toLowerCase();
+        return tagName == "meta" && element.getAttribute("name") == name;
+    }
+    function elementWithoutNonce(element) {
+        if (element.hasAttribute("nonce")) {
+            element.setAttribute("nonce", "");
+        }
+        return element;
+    }
+
+    class PageSnapshot extends Snapshot {
+        constructor(element, headSnapshot) {
+            super(element);
+            this.headSnapshot = headSnapshot;
+        }
+        static fromHTMLString(html = "") {
+            return this.fromDocument(parseHTMLDocument(html));
+        }
+        static fromElement(element) {
+            return this.fromDocument(element.ownerDocument);
+        }
+        static fromDocument({ head, body }) {
+            return new this(body, new HeadSnapshot(head));
+        }
+        clone() {
+            return new PageSnapshot(this.element.cloneNode(true), this.headSnapshot);
+        }
+        get headElement() {
+            return this.headSnapshot.element;
+        }
+        get rootLocation() {
+            var _a;
+            const root = (_a = this.getSetting("root")) !== null && _a !== void 0 ? _a : "/";
+            return expandURL(root);
+        }
+        get cacheControlValue() {
+            return this.getSetting("cache-control");
+        }
+        get isPreviewable() {
+            return this.cacheControlValue != "no-preview";
+        }
+        get isCacheable() {
+            return this.cacheControlValue != "no-cache";
+        }
+        get isVisitable() {
+            return this.getSetting("visit-control") != "reload";
+        }
+        getSetting(name) {
+            return this.headSnapshot.getMetaValue(`turbo-${name}`);
+        }
+    }
+
+    var TimingMetric;
+    (function (TimingMetric) {
+        TimingMetric["visitStart"] = "visitStart";
+        TimingMetric["requestStart"] = "requestStart";
+        TimingMetric["requestEnd"] = "requestEnd";
+        TimingMetric["visitEnd"] = "visitEnd";
+    })(TimingMetric || (TimingMetric = {}));
+    var VisitState;
+    (function (VisitState) {
+        VisitState["initialized"] = "initialized";
+        VisitState["started"] = "started";
+        VisitState["canceled"] = "canceled";
+        VisitState["failed"] = "failed";
+        VisitState["completed"] = "completed";
+    })(VisitState || (VisitState = {}));
+    const defaultOptions = {
+        action: "advance",
+        historyChanged: false,
+        visitCachedSnapshot: () => { },
+        willRender: true,
+    };
+    var SystemStatusCode;
+    (function (SystemStatusCode) {
+        SystemStatusCode[SystemStatusCode["networkFailure"] = 0] = "networkFailure";
+        SystemStatusCode[SystemStatusCode["timeoutFailure"] = -1] = "timeoutFailure";
+        SystemStatusCode[SystemStatusCode["contentTypeMismatch"] = -2] = "contentTypeMismatch";
+    })(SystemStatusCode || (SystemStatusCode = {}));
+    class Visit {
+        constructor(delegate, location, restorationIdentifier, options = {}) {
+            this.identifier = uuid();
+            this.timingMetrics = {};
+            this.followedRedirect = false;
+            this.historyChanged = false;
+            this.scrolled = false;
+            this.snapshotCached = false;
+            this.state = VisitState.initialized;
+            this.delegate = delegate;
+            this.location = location;
+            this.restorationIdentifier = restorationIdentifier || uuid();
+            const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender } = Object.assign(Object.assign({}, defaultOptions), options);
+            this.action = action;
+            this.historyChanged = historyChanged;
+            this.referrer = referrer;
+            this.snapshotHTML = snapshotHTML;
+            this.response = response;
+            this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
+            this.visitCachedSnapshot = visitCachedSnapshot;
+            this.willRender = willRender;
+            this.scrolled = !willRender;
+        }
+        get adapter() {
+            return this.delegate.adapter;
+        }
+        get view() {
+            return this.delegate.view;
+        }
+        get history() {
+            return this.delegate.history;
+        }
+        get restorationData() {
+            return this.history.getRestorationDataForIdentifier(this.restorationIdentifier);
+        }
+        get silent() {
+            return this.isSamePage;
+        }
+        start() {
+            if (this.state == VisitState.initialized) {
+                this.recordTimingMetric(TimingMetric.visitStart);
+                this.state = VisitState.started;
+                this.adapter.visitStarted(this);
+                this.delegate.visitStarted(this);
+            }
+        }
+        cancel() {
+            if (this.state == VisitState.started) {
+                if (this.request) {
+                    this.request.cancel();
+                }
+                this.cancelRender();
+                this.state = VisitState.canceled;
+            }
+        }
+        complete() {
+            if (this.state == VisitState.started) {
+                this.recordTimingMetric(TimingMetric.visitEnd);
+                this.state = VisitState.completed;
+                this.adapter.visitCompleted(this);
+                this.delegate.visitCompleted(this);
+                this.followRedirect();
+            }
+        }
+        fail() {
+            if (this.state == VisitState.started) {
+                this.state = VisitState.failed;
+                this.adapter.visitFailed(this);
+            }
+        }
+        changeHistory() {
+            var _a;
+            if (!this.historyChanged) {
+                const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? "replace" : this.action;
+                const method = this.getHistoryMethodForAction(actionForHistory);
+                this.history.update(method, this.location, this.restorationIdentifier);
+                this.historyChanged = true;
+            }
+        }
+        issueRequest() {
+            if (this.hasPreloadedResponse()) {
+                this.simulateRequest();
+            }
+            else if (this.shouldIssueRequest() && !this.request) {
+                this.request = new FetchRequest(this, FetchMethod.get, this.location);
+                this.request.perform();
+            }
+        }
+        simulateRequest() {
+            if (this.response) {
+                this.startRequest();
+                this.recordResponse();
+                this.finishRequest();
+            }
+        }
+        startRequest() {
+            this.recordTimingMetric(TimingMetric.requestStart);
+            this.adapter.visitRequestStarted(this);
+        }
+        recordResponse(response = this.response) {
+            this.response = response;
+            if (response) {
+                const { statusCode } = response;
+                if (isSuccessful(statusCode)) {
+                    this.adapter.visitRequestCompleted(this);
+                }
+                else {
+                    this.adapter.visitRequestFailedWithStatusCode(this, statusCode);
+                }
+            }
+        }
+        finishRequest() {
+            this.recordTimingMetric(TimingMetric.requestEnd);
+            this.adapter.visitRequestFinished(this);
+        }
+        loadResponse() {
+            if (this.response) {
+                const { statusCode, responseHTML } = this.response;
+                this.render(async () => {
+                    this.cacheSnapshot();
+                    if (this.view.renderPromise)
+                        await this.view.renderPromise;
+                    if (isSuccessful(statusCode) && responseHTML != null) {
+                        await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender);
+                        this.adapter.visitRendered(this);
+                        this.complete();
+                    }
+                    else {
+                        await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML));
+                        this.adapter.visitRendered(this);
+                        this.fail();
+                    }
+                });
+            }
+        }
+        getCachedSnapshot() {
+            const snapshot = this.view.getCachedSnapshotForLocation(this.location) || this.getPreloadedSnapshot();
+            if (snapshot && (!getAnchor(this.location) || snapshot.hasAnchor(getAnchor(this.location)))) {
+                if (this.action == "restore" || snapshot.isPreviewable) {
+                    return snapshot;
+                }
+            }
+        }
+        getPreloadedSnapshot() {
+            if (this.snapshotHTML) {
+                return PageSnapshot.fromHTMLString(this.snapshotHTML);
+            }
+        }
+        hasCachedSnapshot() {
+            return this.getCachedSnapshot() != null;
+        }
+        loadCachedSnapshot() {
+            const snapshot = this.getCachedSnapshot();
+            if (snapshot) {
+                const isPreview = this.shouldIssueRequest();
+                this.render(async () => {
+                    this.cacheSnapshot();
+                    if (this.isSamePage) {
+                        this.adapter.visitRendered(this);
+                    }
+                    else {
+                        if (this.view.renderPromise)
+                            await this.view.renderPromise;
+                        await this.view.renderPage(snapshot, isPreview, this.willRender);
+                        this.adapter.visitRendered(this);
+                        if (!isPreview) {
+                            this.complete();
+                        }
+                    }
+                });
+            }
+        }
+        followRedirect() {
+            var _a;
+            if (this.redirectedToLocation && !this.followedRedirect && ((_a = this.response) === null || _a === void 0 ? void 0 : _a.redirected)) {
+                this.adapter.visitProposedToLocation(this.redirectedToLocation, {
+                    action: 'replace',
+                    response: this.response
+                });
+                this.followedRedirect = true;
+            }
+        }
+        goToSamePageAnchor() {
+            if (this.isSamePage) {
+                this.render(async () => {
+                    this.cacheSnapshot();
+                    this.adapter.visitRendered(this);
+                });
+            }
+        }
+        requestStarted() {
+            this.startRequest();
+        }
+        requestPreventedHandlingResponse(request, response) {
+        }
+        async requestSucceededWithResponse(request, response) {
+            const responseHTML = await response.responseHTML;
+            const { redirected, statusCode } = response;
+            if (responseHTML == undefined) {
+                this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
+            }
+            else {
+                this.redirectedToLocation = response.redirected ? response.location : undefined;
+                this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
+            }
+        }
+        async requestFailedWithResponse(request, response) {
+            const responseHTML = await response.responseHTML;
+            const { redirected, statusCode } = response;
+            if (responseHTML == undefined) {
+                this.recordResponse({ statusCode: SystemStatusCode.contentTypeMismatch, redirected });
+            }
+            else {
+                this.recordResponse({ statusCode: statusCode, responseHTML, redirected });
+            }
+        }
+        requestErrored(request, error) {
+            this.recordResponse({ statusCode: SystemStatusCode.networkFailure, redirected: false });
+        }
+        requestFinished() {
+            this.finishRequest();
+        }
+        performScroll() {
+            if (!this.scrolled) {
+                if (this.action == "restore") {
+                    this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();
+                }
+                else {
+                    this.scrollToAnchor() || this.view.scrollToTop();
+                }
+                if (this.isSamePage) {
+                    this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation, this.location);
+                }
+                this.scrolled = true;
+            }
+        }
+        scrollToRestoredPosition() {
+            const { scrollPosition } = this.restorationData;
+            if (scrollPosition) {
+                this.view.scrollToPosition(scrollPosition);
+                return true;
+            }
+        }
+        scrollToAnchor() {
+            const anchor = getAnchor(this.location);
+            if (anchor != null) {
+                this.view.scrollToAnchor(anchor);
+                return true;
+            }
+        }
+        recordTimingMetric(metric) {
+            this.timingMetrics[metric] = new Date().getTime();
+        }
+        getTimingMetrics() {
+            return Object.assign({}, this.timingMetrics);
+        }
+        getHistoryMethodForAction(action) {
+            switch (action) {
+                case "replace": return history.replaceState;
+                case "advance":
+                case "restore": return history.pushState;
+            }
+        }
+        hasPreloadedResponse() {
+            return typeof this.response == "object";
+        }
+        shouldIssueRequest() {
+            if (this.isSamePage) {
+                return false;
+            }
+            else if (this.action == "restore") {
+                return !this.hasCachedSnapshot();
+            }
+            else {
+                return this.willRender;
+            }
+        }
+        cacheSnapshot() {
+            if (!this.snapshotCached) {
+                this.view.cacheSnapshot().then(snapshot => snapshot && this.visitCachedSnapshot(snapshot));
+                this.snapshotCached = true;
+            }
+        }
+        async render(callback) {
+            this.cancelRender();
+            await new Promise(resolve => {
+                this.frame = requestAnimationFrame(() => resolve());
+            });
+            await callback();
+            delete this.frame;
+            this.performScroll();
+        }
+        cancelRender() {
+            if (this.frame) {
+                cancelAnimationFrame(this.frame);
+                delete this.frame;
+            }
+        }
+    }
+    function isSuccessful(statusCode) {
+        return statusCode >= 200 && statusCode < 300;
+    }
+
+    class BrowserAdapter {
+        constructor(session) {
+            this.progressBar = new ProgressBar;
+            this.showProgressBar = () => {
+                this.progressBar.show();
+            };
+            this.session = session;
+        }
+        visitProposedToLocation(location, options) {
+            this.navigator.startVisit(location, uuid(), options);
+        }
+        visitStarted(visit) {
+            visit.loadCachedSnapshot();
+            visit.issueRequest();
+            visit.changeHistory();
+            visit.goToSamePageAnchor();
+        }
+        visitRequestStarted(visit) {
+            this.progressBar.setValue(0);
+            if (visit.hasCachedSnapshot() || visit.action != "restore") {
+                this.showVisitProgressBarAfterDelay();
+            }
+            else {
+                this.showProgressBar();
+            }
+        }
+        visitRequestCompleted(visit) {
+            visit.loadResponse();
+        }
+        visitRequestFailedWithStatusCode(visit, statusCode) {
+            switch (statusCode) {
+                case SystemStatusCode.networkFailure:
+                case SystemStatusCode.timeoutFailure:
+                case SystemStatusCode.contentTypeMismatch:
+                    return this.reload();
+                default:
+                    return visit.loadResponse();
+            }
+        }
+        visitRequestFinished(visit) {
+            this.progressBar.setValue(1);
+            this.hideVisitProgressBar();
+        }
+        visitCompleted(visit) {
+        }
+        pageInvalidated() {
+            this.reload();
+        }
+        visitFailed(visit) {
+        }
+        visitRendered(visit) {
+        }
+        formSubmissionStarted(formSubmission) {
+            this.progressBar.setValue(0);
+            this.showFormProgressBarAfterDelay();
+        }
+        formSubmissionFinished(formSubmission) {
+            this.progressBar.setValue(1);
+            this.hideFormProgressBar();
+        }
+        showVisitProgressBarAfterDelay() {
+            this.visitProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay);
+        }
+        hideVisitProgressBar() {
+            this.progressBar.hide();
+            if (this.visitProgressBarTimeout != null) {
+                window.clearTimeout(this.visitProgressBarTimeout);
+                delete this.visitProgressBarTimeout;
+            }
+        }
+        showFormProgressBarAfterDelay() {
+            if (this.formProgressBarTimeout == null) {
+                this.formProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay);
+            }
+        }
+        hideFormProgressBar() {
+            this.progressBar.hide();
+            if (this.formProgressBarTimeout != null) {
+                window.clearTimeout(this.formProgressBarTimeout);
+                delete this.formProgressBarTimeout;
+            }
+        }
+        reload() {
+            window.location.reload();
+        }
+        get navigator() {
+            return this.session.navigator;
+        }
+    }
+
+    class CacheObserver {
+        constructor() {
+            this.started = false;
+        }
+        start() {
+            if (!this.started) {
+                this.started = true;
+                addEventListener("turbo:before-cache", this.removeStaleElements, false);
+            }
+        }
+        stop() {
+            if (this.started) {
+                this.started = false;
+                removeEventListener("turbo:before-cache", this.removeStaleElements, false);
+            }
+        }
+        removeStaleElements() {
+            const staleElements = [...document.querySelectorAll('[data-turbo-cache="false"]')];
+            for (const element of staleElements) {
+                element.remove();
+            }
+        }
+    }
+
+    class FormSubmitObserver {
+        constructor(delegate) {
+            this.started = false;
+            this.submitCaptured = () => {
+                removeEventListener("submit", this.submitBubbled, false);
+                addEventListener("submit", this.submitBubbled, false);
+            };
+            this.submitBubbled = ((event) => {
+                if (!event.defaultPrevented) {
+                    const form = event.target instanceof HTMLFormElement ? event.target : undefined;
+                    const submitter = event.submitter || undefined;
+                    if (form) {
+                        const method = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("formmethod")) || form.getAttribute("method");
+                        if (method != "dialog" && this.delegate.willSubmitForm(form, submitter)) {
+                            event.preventDefault();
+                            this.delegate.formSubmitted(form, submitter);
+                        }
+                    }
+                }
+            });
+            this.delegate = delegate;
+        }
+        start() {
+            if (!this.started) {
+                addEventListener("submit", this.submitCaptured, true);
+                this.started = true;
+            }
+        }
+        stop() {
+            if (this.started) {
+                removeEventListener("submit", this.submitCaptured, true);
+                this.started = false;
+            }
+        }
+    }
+
+    class FrameRedirector {
+        constructor(element) {
+            this.element = element;
+            this.linkInterceptor = new LinkInterceptor(this, element);
+            this.formInterceptor = new FormInterceptor(this, element);
+        }
+        start() {
+            this.linkInterceptor.start();
+            this.formInterceptor.start();
+        }
+        stop() {
+            this.linkInterceptor.stop();
+            this.formInterceptor.stop();
+        }
+        shouldInterceptLinkClick(element, url) {
+            return this.shouldRedirect(element);
+        }
+        linkClickIntercepted(element, url) {
+            const frame = this.findFrameElement(element);
+            if (frame) {
+                frame.delegate.linkClickIntercepted(element, url);
+            }
+        }
+        shouldInterceptFormSubmission(element, submitter) {
+            return this.shouldSubmit(element, submitter);
+        }
+        formSubmissionIntercepted(element, submitter) {
+            const frame = this.findFrameElement(element, submitter);
+            if (frame) {
+                frame.removeAttribute("reloadable");
+                frame.delegate.formSubmissionIntercepted(element, submitter);
+            }
+        }
+        shouldSubmit(form, submitter) {
+            var _a;
+            const action = getAction(form, submitter);
+            const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
+            const rootLocation = expandURL((_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/");
+            return this.shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation);
+        }
+        shouldRedirect(element, submitter) {
+            const frame = this.findFrameElement(element, submitter);
+            return frame ? frame != element.closest("turbo-frame") : false;
+        }
+        findFrameElement(element, submitter) {
+            const id = (submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute("data-turbo-frame")) || element.getAttribute("data-turbo-frame");
+            if (id && id != "_top") {
+                const frame = this.element.querySelector(`#${id}:not([disabled])`);
+                if (frame instanceof FrameElement) {
+                    return frame;
+                }
+            }
+        }
+    }
+
+    class History {
+        constructor(delegate) {
+            this.restorationIdentifier = uuid();
+            this.restorationData = {};
+            this.started = false;
+            this.pageLoaded = false;
+            this.onPopState = (event) => {
+                if (this.shouldHandlePopState()) {
+                    const { turbo } = event.state || {};
+                    if (turbo) {
+                        this.location = new URL(window.location.href);
+                        const { restorationIdentifier } = turbo;
+                        this.restorationIdentifier = restorationIdentifier;
+                        this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);
+                    }
+                }
+            };
+            this.onPageLoad = async (event) => {
+                await nextMicrotask();
+                this.pageLoaded = true;
+            };
+            this.delegate = delegate;
+        }
+        start() {
+            if (!this.started) {
+                addEventListener("popstate", this.onPopState, false);
+                addEventListener("load", this.onPageLoad, false);
+                this.started = true;
+                this.replace(new URL(window.location.href));
+            }
+        }
+        stop() {
+            if (this.started) {
+                removeEventListener("popstate", this.onPopState, false);
+                removeEventListener("load", this.onPageLoad, false);
+                this.started = false;
+            }
+        }
+        push(location, restorationIdentifier) {
+            this.update(history.pushState, location, restorationIdentifier);
+        }
+        replace(location, restorationIdentifier) {
+            this.update(history.replaceState, location, restorationIdentifier);
+        }
+        update(method, location, restorationIdentifier = uuid()) {
+            const state = { turbo: { restorationIdentifier } };
+            method.call(history, state, "", location.href);
+            this.location = location;
+            this.restorationIdentifier = restorationIdentifier;
+        }
+        getRestorationDataForIdentifier(restorationIdentifier) {
+            return this.restorationData[restorationIdentifier] || {};
+        }
+        updateRestorationData(additionalData) {
+            const { restorationIdentifier } = this;
+            const restorationData = this.restorationData[restorationIdentifier];
+            this.restorationData[restorationIdentifier] = Object.assign(Object.assign({}, restorationData), additionalData);
+        }
+        assumeControlOfScrollRestoration() {
+            var _a;
+            if (!this.previousScrollRestoration) {
+                this.previousScrollRestoration = (_a = history.scrollRestoration) !== null && _a !== void 0 ? _a : "auto";
+                history.scrollRestoration = "manual";
+            }
+        }
+        relinquishControlOfScrollRestoration() {
+            if (this.previousScrollRestoration) {
+                history.scrollRestoration = this.previousScrollRestoration;
+                delete this.previousScrollRestoration;
+            }
+        }
+        shouldHandlePopState() {
+            return this.pageIsLoaded();
+        }
+        pageIsLoaded() {
+            return this.pageLoaded || document.readyState == "complete";
+        }
+    }
+
+    class LinkClickObserver {
+        constructor(delegate) {
+            this.started = false;
+            this.clickCaptured = () => {
+                removeEventListener("click", this.clickBubbled, false);
+                addEventListener("click", this.clickBubbled, false);
+            };
+            this.clickBubbled = (event) => {
+                if (this.clickEventIsSignificant(event)) {
+                    const target = (event.composedPath && event.composedPath()[0]) || event.target;
+                    const link = this.findLinkFromClickTarget(target);
+                    if (link) {
+                        const location = this.getLocationForLink(link);
+                        if (this.delegate.willFollowLinkToLocation(link, location)) {
+                            event.preventDefault();
+                            this.delegate.followedLinkToLocation(link, location);
+                        }
+                    }
+                }
+            };
+            this.delegate = delegate;
+        }
+        start() {
+            if (!this.started) {
+                addEventListener("click", this.clickCaptured, true);
+                this.started = true;
+            }
+        }
+        stop() {
+            if (this.started) {
+                removeEventListener("click", this.clickCaptured, true);
+                this.started = false;
+            }
+        }
+        clickEventIsSignificant(event) {
+            return !((event.target && event.target.isContentEditable)
+                || event.defaultPrevented
+                || event.which > 1
+                || event.altKey
+                || event.ctrlKey
+                || event.metaKey
+                || event.shiftKey);
+        }
+        findLinkFromClickTarget(target) {
+            if (target instanceof Element) {
+                return target.closest("a[href]:not([target^=_]):not([download])");
+            }
+        }
+        getLocationForLink(link) {
+            return expandURL(link.getAttribute("href") || "");
+        }
+    }
+
+    function isAction(action) {
+        return action == "advance" || action == "replace" || action == "restore";
+    }
+
+    class Navigator {
+        constructor(delegate) {
+            this.delegate = delegate;
+        }
+        proposeVisit(location, options = {}) {
+            if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {
+                if (locationIsVisitable(location, this.view.snapshot.rootLocation)) {
+                    this.delegate.visitProposedToLocation(location, options);
+                }
+                else {
+                    window.location.href = location.toString();
+                }
+            }
+        }
+        startVisit(locatable, restorationIdentifier, options = {}) {
+            this.stop();
+            this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({ referrer: this.location }, options));
+            this.currentVisit.start();
+        }
+        submitForm(form, submitter) {
+            this.stop();
+            this.formSubmission = new FormSubmission(this, form, submitter, true);
+            this.formSubmission.start();
+        }
+        stop() {
+            if (this.formSubmission) {
+                this.formSubmission.stop();
+                delete this.formSubmission;
+            }
+            if (this.currentVisit) {
+                this.currentVisit.cancel();
+                delete this.currentVisit;
+            }
+        }
+        get adapter() {
+            return this.delegate.adapter;
+        }
+        get view() {
+            return this.delegate.view;
+        }
+        get history() {
+            return this.delegate.history;
+        }
+        formSubmissionStarted(formSubmission) {
+            if (typeof this.adapter.formSubmissionStarted === 'function') {
+                this.adapter.formSubmissionStarted(formSubmission);
+            }
+        }
+        async formSubmissionSucceededWithResponse(formSubmission, fetchResponse) {
+            if (formSubmission == this.formSubmission) {
+                const responseHTML = await fetchResponse.responseHTML;
+                if (responseHTML) {
+                    if (formSubmission.method != FetchMethod.get) {
+                        this.view.clearSnapshotCache();
+                    }
+                    const { statusCode, redirected } = fetchResponse;
+                    const action = this.getActionForFormSubmission(formSubmission);
+                    const visitOptions = { action, response: { statusCode, responseHTML, redirected } };
+                    this.proposeVisit(fetchResponse.location, visitOptions);
+                }
+            }
+        }
+        async formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
+            const responseHTML = await fetchResponse.responseHTML;
+            if (responseHTML) {
+                const snapshot = PageSnapshot.fromHTMLString(responseHTML);
+                if (fetchResponse.serverError) {
+                    await this.view.renderError(snapshot);
+                }
+                else {
+                    await this.view.renderPage(snapshot);
+                }
+                this.view.scrollToTop();
+                this.view.clearSnapshotCache();
+            }
+        }
+        formSubmissionErrored(formSubmission, error) {
+            console.error(error);
+        }
+        formSubmissionFinished(formSubmission) {
+            if (typeof this.adapter.formSubmissionFinished === 'function') {
+                this.adapter.formSubmissionFinished(formSubmission);
+            }
+        }
+        visitStarted(visit) {
+            this.delegate.visitStarted(visit);
+        }
+        visitCompleted(visit) {
+            this.delegate.visitCompleted(visit);
+        }
+        locationWithActionIsSamePage(location, action) {
+            const anchor = getAnchor(location);
+            const currentAnchor = getAnchor(this.view.lastRenderedLocation);
+            const isRestorationToTop = action === 'restore' && typeof anchor === 'undefined';
+            return action !== "replace" &&
+                getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) &&
+                (isRestorationToTop || (anchor != null && anchor !== currentAnchor));
+        }
+        visitScrolledToSamePageLocation(oldURL, newURL) {
+            this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);
+        }
+        get location() {
+            return this.history.location;
+        }
+        get restorationIdentifier() {
+            return this.history.restorationIdentifier;
+        }
+        getActionForFormSubmission(formSubmission) {
+            const { formElement, submitter } = formSubmission;
+            const action = getAttribute("data-turbo-action", submitter, formElement);
+            return isAction(action) ? action : "advance";
+        }
+    }
+
+    var PageStage;
+    (function (PageStage) {
+        PageStage[PageStage["initial"] = 0] = "initial";
+        PageStage[PageStage["loading"] = 1] = "loading";
+        PageStage[PageStage["interactive"] = 2] = "interactive";
+        PageStage[PageStage["complete"] = 3] = "complete";
+    })(PageStage || (PageStage = {}));
+    class PageObserver {
+        constructor(delegate) {
+            this.stage = PageStage.initial;
+            this.started = false;
+            this.interpretReadyState = () => {
+                const { readyState } = this;
+                if (readyState == "interactive") {
+                    this.pageIsInteractive();
+                }
+                else if (readyState == "complete") {
+                    this.pageIsComplete();
+                }
+            };
+            this.pageWillUnload = () => {
+                this.delegate.pageWillUnload();
+            };
+            this.delegate = delegate;
+        }
+        start() {
+            if (!this.started) {
+                if (this.stage == PageStage.initial) {
+                    this.stage = PageStage.loading;
+                }
+                document.addEventListener("readystatechange", this.interpretReadyState, false);
+                addEventListener("pagehide", this.pageWillUnload, false);
+                this.started = true;
+            }
+        }
+        stop() {
+            if (this.started) {
+                document.removeEventListener("readystatechange", this.interpretReadyState, false);
+                removeEventListener("pagehide", this.pageWillUnload, false);
+                this.started = false;
+            }
+        }
+        pageIsInteractive() {
+            if (this.stage == PageStage.loading) {
+                this.stage = PageStage.interactive;
+                this.delegate.pageBecameInteractive();
+            }
+        }
+        pageIsComplete() {
+            this.pageIsInteractive();
+            if (this.stage == PageStage.interactive) {
+                this.stage = PageStage.complete;
+                this.delegate.pageLoaded();
+            }
+        }
+        get readyState() {
+            return document.readyState;
+        }
+    }
+
+    class ScrollObserver {
+        constructor(delegate) {
+            this.started = false;
+            this.onScroll = () => {
+                this.updatePosition({ x: window.pageXOffset, y: window.pageYOffset });
+            };
+            this.delegate = delegate;
+        }
+        start() {
+            if (!this.started) {
+                addEventListener("scroll", this.onScroll, false);
+                this.onScroll();
+                this.started = true;
+            }
+        }
+        stop() {
+            if (this.started) {
+                removeEventListener("scroll", this.onScroll, false);
+                this.started = false;
+            }
+        }
+        updatePosition(position) {
+            this.delegate.scrollPositionChanged(position);
+        }
+    }
+
+    class StreamObserver {
+        constructor(delegate) {
+            this.sources = new Set;
+            this.started = false;
+            this.inspectFetchResponse = ((event) => {
+                const response = fetchResponseFromEvent(event);
+                if (response && fetchResponseIsStream(response)) {
+                    event.preventDefault();
+                    this.receiveMessageResponse(response);
+                }
+            });
+            this.receiveMessageEvent = (event) => {
+                if (this.started && typeof event.data == "string") {
+                    this.receiveMessageHTML(event.data);
+                }
+            };
+            this.delegate = delegate;
+        }
+        start() {
+            if (!this.started) {
+                this.started = true;
+                addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
+            }
+        }
+        stop() {
+            if (this.started) {
+                this.started = false;
+                removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
+            }
+        }
+        connectStreamSource(source) {
+            if (!this.streamSourceIsConnected(source)) {
+                this.sources.add(source);
+                source.addEventListener("message", this.receiveMessageEvent, false);
+            }
+        }
+        disconnectStreamSource(source) {
+            if (this.streamSourceIsConnected(source)) {
+                this.sources.delete(source);
+                source.removeEventListener("message", this.receiveMessageEvent, false);
+            }
+        }
+        streamSourceIsConnected(source) {
+            return this.sources.has(source);
+        }
+        async receiveMessageResponse(response) {
+            const html = await response.responseHTML;
+            if (html) {
+                this.receiveMessageHTML(html);
+            }
+        }
+        receiveMessageHTML(html) {
+            this.delegate.receivedMessageFromStream(new StreamMessage(html));
+        }
+    }
+    function fetchResponseFromEvent(event) {
+        var _a;
+        const fetchResponse = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchResponse;
+        if (fetchResponse instanceof FetchResponse) {
+            return fetchResponse;
+        }
+    }
+    function fetchResponseIsStream(response) {
+        var _a;
+        const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : "";
+        return contentType.startsWith(StreamMessage.contentType);
+    }
+
+    class ErrorRenderer extends Renderer {
+        async render() {
+            this.replaceHeadAndBody();
+            this.activateScriptElements();
+        }
+        replaceHeadAndBody() {
+            const { documentElement, head, body } = document;
+            documentElement.replaceChild(this.newHead, head);
+            documentElement.replaceChild(this.newElement, body);
+        }
+        activateScriptElements() {
+            for (const replaceableElement of this.scriptElements) {
+                const parentNode = replaceableElement.parentNode;
+                if (parentNode) {
+                    const element = this.createScriptElement(replaceableElement);
+                    parentNode.replaceChild(element, replaceableElement);
+                }
+            }
+        }
+        get newHead() {
+            return this.newSnapshot.headSnapshot.element;
+        }
+        get scriptElements() {
+            return [...document.documentElement.querySelectorAll("script")];
+        }
+    }
+
+    class PageRenderer extends Renderer {
+        get shouldRender() {
+            return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
+        }
+        prepareToRender() {
+            this.mergeHead();
+        }
+        async render() {
+            if (this.willRender) {
+                this.replaceBody();
+            }
+        }
+        finishRendering() {
+            super.finishRendering();
+            if (!this.isPreview) {
+                this.focusFirstAutofocusableElement();
+            }
+        }
+        get currentHeadSnapshot() {
+            return this.currentSnapshot.headSnapshot;
+        }
+        get newHeadSnapshot() {
+            return this.newSnapshot.headSnapshot;
+        }
+        get newElement() {
+            return this.newSnapshot.element;
+        }
+        mergeHead() {
+            this.copyNewHeadStylesheetElements();
+            this.copyNewHeadScriptElements();
+            this.removeCurrentHeadProvisionalElements();
+            this.copyNewHeadProvisionalElements();
+        }
+        replaceBody() {
+            this.preservingPermanentElements(() => {
+                this.activateNewBody();
+                this.assignNewBody();
+            });
+        }
+        get trackedElementsAreIdentical() {
+            return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;
+        }
+        copyNewHeadStylesheetElements() {
+            for (const element of this.newHeadStylesheetElements) {
+                document.head.appendChild(element);
+            }
+        }
+        copyNewHeadScriptElements() {
+            for (const element of this.newHeadScriptElements) {
+                document.head.appendChild(this.createScriptElement(element));
+            }
+        }
+        removeCurrentHeadProvisionalElements() {
+            for (const element of this.currentHeadProvisionalElements) {
+                document.head.removeChild(element);
+            }
+        }
+        copyNewHeadProvisionalElements() {
+            for (const element of this.newHeadProvisionalElements) {
+                document.head.appendChild(element);
+            }
+        }
+        activateNewBody() {
+            document.adoptNode(this.newElement);
+            this.activateNewBodyScriptElements();
+        }
+        activateNewBodyScriptElements() {
+            for (const inertScriptElement of this.newBodyScriptElements) {
+                const activatedScriptElement = this.createScriptElement(inertScriptElement);
+                inertScriptElement.replaceWith(activatedScriptElement);
+            }
+        }
+        assignNewBody() {
+            if (document.body && this.newElement instanceof HTMLBodyElement) {
+                document.body.replaceWith(this.newElement);
+            }
+            else {
+                document.documentElement.appendChild(this.newElement);
+            }
+        }
+        get newHeadStylesheetElements() {
+            return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
+        }
+        get newHeadScriptElements() {
+            return this.newHeadSnapshot.getScriptElementsNotInSnapshot(this.currentHeadSnapshot);
+        }
+        get currentHeadProvisionalElements() {
+            return this.currentHeadSnapshot.provisionalElements;
+        }
+        get newHeadProvisionalElements() {
+            return this.newHeadSnapshot.provisionalElements;
+        }
+        get newBodyScriptElements() {
+            return this.newElement.querySelectorAll("script");
+        }
+    }
+
+    class SnapshotCache {
+        constructor(size) {
+            this.keys = [];
+            this.snapshots = {};
+            this.size = size;
+        }
+        has(location) {
+            return toCacheKey(location) in this.snapshots;
+        }
+        get(location) {
+            if (this.has(location)) {
+                const snapshot = this.read(location);
+                this.touch(location);
+                return snapshot;
+            }
+        }
+        put(location, snapshot) {
+            this.write(location, snapshot);
+            this.touch(location);
+            return snapshot;
+        }
+        clear() {
+            this.snapshots = {};
+        }
+        read(location) {
+            return this.snapshots[toCacheKey(location)];
+        }
+        write(location, snapshot) {
+            this.snapshots[toCacheKey(location)] = snapshot;
+        }
+        touch(location) {
+            const key = toCacheKey(location);
+            const index = this.keys.indexOf(key);
+            if (index > -1)
+                this.keys.splice(index, 1);
+            this.keys.unshift(key);
+            this.trim();
+        }
+        trim() {
+            for (const key of this.keys.splice(this.size)) {
+                delete this.snapshots[key];
+            }
+        }
+    }
+
+    class PageView extends View {
+        constructor() {
+            super(...arguments);
+            this.snapshotCache = new SnapshotCache(10);
+            this.lastRenderedLocation = new URL(location.href);
+        }
+        renderPage(snapshot, isPreview = false, willRender = true) {
+            const renderer = new PageRenderer(this.snapshot, snapshot, isPreview, willRender);
+            return this.render(renderer);
+        }
+        renderError(snapshot) {
+            const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
+            return this.render(renderer);
+        }
+        clearSnapshotCache() {
+            this.snapshotCache.clear();
+        }
+        async cacheSnapshot() {
+            if (this.shouldCacheSnapshot) {
+                this.delegate.viewWillCacheSnapshot();
+                const { snapshot, lastRenderedLocation: location } = this;
+                await nextEventLoopTick();
+                const cachedSnapshot = snapshot.clone();
+                this.snapshotCache.put(location, cachedSnapshot);
+                return cachedSnapshot;
+            }
+        }
+        getCachedSnapshotForLocation(location) {
+            return this.snapshotCache.get(location);
+        }
+        get snapshot() {
+            return PageSnapshot.fromElement(this.element);
+        }
+        get shouldCacheSnapshot() {
+            return this.snapshot.isCacheable;
+        }
+    }
+
+    class Session {
+        constructor() {
+            this.navigator = new Navigator(this);
+            this.history = new History(this);
+            this.view = new PageView(this, document.documentElement);
+            this.adapter = new BrowserAdapter(this);
+            this.pageObserver = new PageObserver(this);
+            this.cacheObserver = new CacheObserver();
+            this.linkClickObserver = new LinkClickObserver(this);
+            this.formSubmitObserver = new FormSubmitObserver(this);
+            this.scrollObserver = new ScrollObserver(this);
+            this.streamObserver = new StreamObserver(this);
+            this.frameRedirector = new FrameRedirector(document.documentElement);
+            this.drive = true;
+            this.enabled = true;
+            this.progressBarDelay = 500;
+            this.started = false;
+        }
+        start() {
+            if (!this.started) {
+                this.pageObserver.start();
+                this.cacheObserver.start();
+                this.linkClickObserver.start();
+                this.formSubmitObserver.start();
+                this.scrollObserver.start();
+                this.streamObserver.start();
+                this.frameRedirector.start();
+                this.history.start();
+                this.started = true;
+                this.enabled = true;
+            }
+        }
+        disable() {
+            this.enabled = false;
+        }
+        stop() {
+            if (this.started) {
+                this.pageObserver.stop();
+                this.cacheObserver.stop();
+                this.linkClickObserver.stop();
+                this.formSubmitObserver.stop();
+                this.scrollObserver.stop();
+                this.streamObserver.stop();
+                this.frameRedirector.stop();
+                this.history.stop();
+                this.started = false;
+            }
+        }
+        registerAdapter(adapter) {
+            this.adapter = adapter;
+        }
+        visit(location, options = {}) {
+            this.navigator.proposeVisit(expandURL(location), options);
+        }
+        connectStreamSource(source) {
+            this.streamObserver.connectStreamSource(source);
+        }
+        disconnectStreamSource(source) {
+            this.streamObserver.disconnectStreamSource(source);
+        }
+        renderStreamMessage(message) {
+            document.documentElement.appendChild(StreamMessage.wrap(message).fragment);
+        }
+        clearCache() {
+            this.view.clearSnapshotCache();
+        }
+        setProgressBarDelay(delay) {
+            this.progressBarDelay = delay;
+        }
+        get location() {
+            return this.history.location;
+        }
+        get restorationIdentifier() {
+            return this.history.restorationIdentifier;
+        }
+        historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier) {
+            if (this.enabled) {
+                this.navigator.startVisit(location, restorationIdentifier, { action: "restore", historyChanged: true });
+            }
+            else {
+                this.adapter.pageInvalidated();
+            }
+        }
+        scrollPositionChanged(position) {
+            this.history.updateRestorationData({ scrollPosition: position });
+        }
+        willFollowLinkToLocation(link, location) {
+            return this.elementDriveEnabled(link)
+                && locationIsVisitable(location, this.snapshot.rootLocation)
+                && this.applicationAllowsFollowingLinkToLocation(link, location);
+        }
+        followedLinkToLocation(link, location) {
+            const action = this.getActionForLink(link);
+            this.convertLinkWithMethodClickToFormSubmission(link) || this.visit(location.href, { action });
+        }
+        convertLinkWithMethodClickToFormSubmission(link) {
+            const linkMethod = link.getAttribute("data-turbo-method");
+            if (linkMethod) {
+                const form = document.createElement("form");
+                form.method = linkMethod;
+                form.action = link.getAttribute("href") || "undefined";
+                form.hidden = true;
+                if (link.hasAttribute("data-turbo-confirm")) {
+                    form.setAttribute("data-turbo-confirm", link.getAttribute("data-turbo-confirm"));
+                }
+                const frame = this.getTargetFrameForLink(link);
+                if (frame) {
+                    form.setAttribute("data-turbo-frame", frame);
+                    form.addEventListener("turbo:submit-start", () => form.remove());
+                }
+                else {
+                    form.addEventListener("submit", () => form.remove());
+                }
+                document.body.appendChild(form);
+                return dispatch("submit", { cancelable: true, target: form });
+            }
+            else {
+                return false;
+            }
+        }
+        allowsVisitingLocationWithAction(location, action) {
+            return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location);
+        }
+        visitProposedToLocation(location, options) {
+            extendURLWithDeprecatedProperties(location);
+            this.adapter.visitProposedToLocation(location, options);
+        }
+        visitStarted(visit) {
+            extendURLWithDeprecatedProperties(visit.location);
+            if (!visit.silent) {
+                this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);
+            }
+        }
+        visitCompleted(visit) {
+            this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
+        }
+        locationWithActionIsSamePage(location, action) {
+            return this.navigator.locationWithActionIsSamePage(location, action);
+        }
+        visitScrolledToSamePageLocation(oldURL, newURL) {
+            this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);
+        }
+        willSubmitForm(form, submitter) {
+            const action = getAction(form, submitter);
+            return this.elementDriveEnabled(form)
+                && (!submitter || this.elementDriveEnabled(submitter))
+                && locationIsVisitable(expandURL(action), this.snapshot.rootLocation);
+        }
+        formSubmitted(form, submitter) {
+            this.navigator.submitForm(form, submitter);
+        }
+        pageBecameInteractive() {
+            this.view.lastRenderedLocation = this.location;
+            this.notifyApplicationAfterPageLoad();
+        }
+        pageLoaded() {
+            this.history.assumeControlOfScrollRestoration();
+        }
+        pageWillUnload() {
+            this.history.relinquishControlOfScrollRestoration();
+        }
+        receivedMessageFromStream(message) {
+            this.renderStreamMessage(message);
+        }
+        viewWillCacheSnapshot() {
+            var _a;
+            if (!((_a = this.navigator.currentVisit) === null || _a === void 0 ? void 0 : _a.silent)) {
+                this.notifyApplicationBeforeCachingSnapshot();
+            }
+        }
+        allowsImmediateRender({ element }, resume) {
+            const event = this.notifyApplicationBeforeRender(element, resume);
+            return !event.defaultPrevented;
+        }
+        viewRenderedSnapshot(snapshot, isPreview) {
+            this.view.lastRenderedLocation = this.history.location;
+            this.notifyApplicationAfterRender();
+        }
+        viewInvalidated() {
+            this.adapter.pageInvalidated();
+        }
+        frameLoaded(frame) {
+            this.notifyApplicationAfterFrameLoad(frame);
+        }
+        frameRendered(fetchResponse, frame) {
+            this.notifyApplicationAfterFrameRender(fetchResponse, frame);
+        }
+        applicationAllowsFollowingLinkToLocation(link, location) {
+            const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
+            return !event.defaultPrevented;
+        }
+        applicationAllowsVisitingLocation(location) {
+            const event = this.notifyApplicationBeforeVisitingLocation(location);
+            return !event.defaultPrevented;
+        }
+        notifyApplicationAfterClickingLinkToLocation(link, location) {
+            return dispatch("turbo:click", { target: link, detail: { url: location.href }, cancelable: true });
+        }
+        notifyApplicationBeforeVisitingLocation(location) {
+            return dispatch("turbo:before-visit", { detail: { url: location.href }, cancelable: true });
+        }
+        notifyApplicationAfterVisitingLocation(location, action) {
+            markAsBusy(document.documentElement);
+            return dispatch("turbo:visit", { detail: { url: location.href, action } });
+        }
+        notifyApplicationBeforeCachingSnapshot() {
+            return dispatch("turbo:before-cache");
+        }
+        notifyApplicationBeforeRender(newBody, resume) {
+            return dispatch("turbo:before-render", { detail: { newBody, resume }, cancelable: true });
+        }
+        notifyApplicationAfterRender() {
+            return dispatch("turbo:render");
+        }
+        notifyApplicationAfterPageLoad(timing = {}) {
+            clearBusyState(document.documentElement);
+            return dispatch("turbo:load", { detail: { url: this.location.href, timing } });
+        }
+        notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {
+            dispatchEvent(new HashChangeEvent("hashchange", { oldURL: oldURL.toString(), newURL: newURL.toString() }));
+        }
+        notifyApplicationAfterFrameLoad(frame) {
+            return dispatch("turbo:frame-load", { target: frame });
+        }
+        notifyApplicationAfterFrameRender(fetchResponse, frame) {
+            return dispatch("turbo:frame-render", { detail: { fetchResponse }, target: frame, cancelable: true });
+        }
+        elementDriveEnabled(element) {
+            const container = element === null || element === void 0 ? void 0 : element.closest("[data-turbo]");
+            if (this.drive) {
+                if (container) {
+                    return container.getAttribute("data-turbo") != "false";
+                }
+                else {
+                    return true;
+                }
+            }
+            else {
+                if (container) {
+                    return container.getAttribute("data-turbo") == "true";
+                }
+                else {
+                    return false;
+                }
+            }
+        }
+        getActionForLink(link) {
+            const action = link.getAttribute("data-turbo-action");
+            return isAction(action) ? action : "advance";
+        }
+        getTargetFrameForLink(link) {
+            const frame = link.getAttribute("data-turbo-frame");
+            if (frame) {
+                return frame;
+            }
+            else {
+                const container = link.closest("turbo-frame");
+                if (container) {
+                    return container.id;
+                }
+            }
+        }
+        get snapshot() {
+            return this.view.snapshot;
+        }
+    }
+    function extendURLWithDeprecatedProperties(url) {
+        Object.defineProperties(url, deprecatedLocationPropertyDescriptors);
+    }
+    const deprecatedLocationPropertyDescriptors = {
+        absoluteURL: {
+            get() {
+                return this.toString();
+            }
+        }
+    };
+
+    const session = new Session;
+    const { navigator: navigator$1 } = session;
+    function start() {
+        session.start();
+    }
+    function registerAdapter(adapter) {
+        session.registerAdapter(adapter);
+    }
+    function visit(location, options) {
+        session.visit(location, options);
+    }
+    function connectStreamSource(source) {
+        session.connectStreamSource(source);
+    }
+    function disconnectStreamSource(source) {
+        session.disconnectStreamSource(source);
+    }
+    function renderStreamMessage(message) {
+        session.renderStreamMessage(message);
+    }
+    function clearCache() {
+        session.clearCache();
+    }
+    function setProgressBarDelay(delay) {
+        session.setProgressBarDelay(delay);
+    }
+    function setConfirmMethod(confirmMethod) {
+        FormSubmission.confirmMethod = confirmMethod;
+    }
+
+    var Turbo = /*#__PURE__*/Object.freeze({
+        __proto__: null,
+        navigator: navigator$1,
+        session: session,
+        PageRenderer: PageRenderer,
+        PageSnapshot: PageSnapshot,
+        start: start,
+        registerAdapter: registerAdapter,
+        visit: visit,
+        connectStreamSource: connectStreamSource,
+        disconnectStreamSource: disconnectStreamSource,
+        renderStreamMessage: renderStreamMessage,
+        clearCache: clearCache,
+        setProgressBarDelay: setProgressBarDelay,
+        setConfirmMethod: setConfirmMethod
+    });
+
+    class FrameController {
+        constructor(element) {
+            this.fetchResponseLoaded = (fetchResponse) => { };
+            this.currentFetchRequest = null;
+            this.resolveVisitPromise = () => { };
+            this.connected = false;
+            this.hasBeenLoaded = false;
+            this.settingSourceURL = false;
+            this.element = element;
+            this.view = new FrameView(this, this.element);
+            this.appearanceObserver = new AppearanceObserver(this, this.element);
+            this.linkInterceptor = new LinkInterceptor(this, this.element);
+            this.formInterceptor = new FormInterceptor(this, this.element);
+        }
+        connect() {
+            if (!this.connected) {
+                this.connected = true;
+                this.reloadable = false;
+                if (this.loadingStyle == FrameLoadingStyle.lazy) {
+                    this.appearanceObserver.start();
+                }
+                this.linkInterceptor.start();
+                this.formInterceptor.start();
+                this.sourceURLChanged();
+            }
+        }
+        disconnect() {
+            if (this.connected) {
+                this.connected = false;
+                this.appearanceObserver.stop();
+                this.linkInterceptor.stop();
+                this.formInterceptor.stop();
+            }
+        }
+        disabledChanged() {
+            if (this.loadingStyle == FrameLoadingStyle.eager) {
+                this.loadSourceURL();
+            }
+        }
+        sourceURLChanged() {
+            if (this.loadingStyle == FrameLoadingStyle.eager || this.hasBeenLoaded) {
+                this.loadSourceURL();
+            }
+        }
+        loadingStyleChanged() {
+            if (this.loadingStyle == FrameLoadingStyle.lazy) {
+                this.appearanceObserver.start();
+            }
+            else {
+                this.appearanceObserver.stop();
+                this.loadSourceURL();
+            }
+        }
+        async loadSourceURL() {
+            if (!this.settingSourceURL && this.enabled && this.isActive && (this.reloadable || this.sourceURL != this.currentURL)) {
+                const previousURL = this.currentURL;
+                this.currentURL = this.sourceURL;
+                if (this.sourceURL) {
+                    try {
+                        this.element.loaded = this.visit(expandURL(this.sourceURL));
+                        this.appearanceObserver.stop();
+                        await this.element.loaded;
+                        this.hasBeenLoaded = true;
+                    }
+                    catch (error) {
+                        this.currentURL = previousURL;
+                        throw error;
+                    }
+                }
+            }
+        }
+        async loadResponse(fetchResponse) {
+            if (fetchResponse.redirected || (fetchResponse.succeeded && fetchResponse.isHTML)) {
+                this.sourceURL = fetchResponse.response.url;
+            }
+            try {
+                const html = await fetchResponse.responseHTML;
+                if (html) {
+                    const { body } = parseHTMLDocument(html);
+                    const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
+                    const renderer = new FrameRenderer(this.view.snapshot, snapshot, false, false);
+                    if (this.view.renderPromise)
+                        await this.view.renderPromise;
+                    await this.view.render(renderer);
+                    session.frameRendered(fetchResponse, this.element);
+                    session.frameLoaded(this.element);
+                    this.fetchResponseLoaded(fetchResponse);
+                }
+            }
+            catch (error) {
+                console.error(error);
+                this.view.invalidate();
+            }
+            finally {
+                this.fetchResponseLoaded = () => { };
+            }
+        }
+        elementAppearedInViewport(element) {
+            this.loadSourceURL();
+        }
+        shouldInterceptLinkClick(element, url) {
+            if (element.hasAttribute("data-turbo-method")) {
+                return false;
+            }
+            else {
+                return this.shouldInterceptNavigation(element);
+            }
+        }
+        linkClickIntercepted(element, url) {
+            this.reloadable = true;
+            this.navigateFrame(element, url);
+        }
+        shouldInterceptFormSubmission(element, submitter) {
+            return this.shouldInterceptNavigation(element, submitter);
+        }
+        formSubmissionIntercepted(element, submitter) {
+            if (this.formSubmission) {
+                this.formSubmission.stop();
+            }
+            this.reloadable = false;
+            this.formSubmission = new FormSubmission(this, element, submitter);
+            const { fetchRequest } = this.formSubmission;
+            this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest);
+            this.formSubmission.start();
+        }
+        prepareHeadersForRequest(headers, request) {
+            headers["Turbo-Frame"] = this.id;
+        }
+        requestStarted(request) {
+            markAsBusy(this.element);
+        }
+        requestPreventedHandlingResponse(request, response) {
+            this.resolveVisitPromise();
+        }
+        async requestSucceededWithResponse(request, response) {
+            await this.loadResponse(response);
+            this.resolveVisitPromise();
+        }
+        requestFailedWithResponse(request, response) {
+            console.error(response);
+            this.resolveVisitPromise();
+        }
+        requestErrored(request, error) {
+            console.error(error);
+            this.resolveVisitPromise();
+        }
+        requestFinished(request) {
+            clearBusyState(this.element);
+        }
+        formSubmissionStarted({ formElement }) {
+            markAsBusy(formElement, this.findFrameElement(formElement));
+        }
+        formSubmissionSucceededWithResponse(formSubmission, response) {
+            const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter);
+            this.proposeVisitIfNavigatedWithAction(frame, formSubmission.formElement, formSubmission.submitter);
+            frame.delegate.loadResponse(response);
+        }
+        formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
+            this.element.delegate.loadResponse(fetchResponse);
+        }
+        formSubmissionErrored(formSubmission, error) {
+            console.error(error);
+        }
+        formSubmissionFinished({ formElement }) {
+            clearBusyState(formElement, this.findFrameElement(formElement));
+        }
+        allowsImmediateRender(snapshot, resume) {
+            return true;
+        }
+        viewRenderedSnapshot(snapshot, isPreview) {
+        }
+        viewInvalidated() {
+        }
+        async visit(url) {
+            var _a;
+            const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams, this.element);
+            (_a = this.currentFetchRequest) === null || _a === void 0 ? void 0 : _a.cancel();
+            this.currentFetchRequest = request;
+            return new Promise(resolve => {
+                this.resolveVisitPromise = () => {
+                    this.resolveVisitPromise = () => { };
+                    this.currentFetchRequest = null;
+                    resolve();
+                };
+                request.perform();
+            });
+        }
+        navigateFrame(element, url, submitter) {
+            const frame = this.findFrameElement(element, submitter);
+            this.proposeVisitIfNavigatedWithAction(frame, element, submitter);
+            frame.setAttribute("reloadable", "");
+            frame.src = url;
+        }
+        proposeVisitIfNavigatedWithAction(frame, element, submitter) {
+            const action = getAttribute("data-turbo-action", submitter, element, frame);
+            if (isAction(action)) {
+                const { visitCachedSnapshot } = new SnapshotSubstitution(frame);
+                frame.delegate.fetchResponseLoaded = (fetchResponse) => {
+                    if (frame.src) {
+                        const { statusCode, redirected } = fetchResponse;
+                        const responseHTML = frame.ownerDocument.documentElement.outerHTML;
+                        const response = { statusCode, redirected, responseHTML };
+                        session.visit(frame.src, { action, response, visitCachedSnapshot, willRender: false });
+                    }
+                };
+            }
+        }
+        findFrameElement(element, submitter) {
+            var _a;
+            const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
+            return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
+        }
+        async extractForeignFrameElement(container) {
+            let element;
+            const id = CSS.escape(this.id);
+            try {
+                if (element = activateElement(container.querySelector(`turbo-frame#${id}`), this.currentURL)) {
+                    return element;
+                }
+                if (element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.currentURL)) {
+                    await element.loaded;
+                    return await this.extractForeignFrameElement(element);
+                }
+                console.error(`Response has no matching <turbo-frame id="${id}"> element`);
+            }
+            catch (error) {
+                console.error(error);
+            }
+            return new FrameElement();
+        }
+        formActionIsVisitable(form, submitter) {
+            const action = getAction(form, submitter);
+            return locationIsVisitable(expandURL(action), this.rootLocation);
+        }
+        shouldInterceptNavigation(element, submitter) {
+            const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target");
+            if (element instanceof HTMLFormElement && !this.formActionIsVisitable(element, submitter)) {
+                return false;
+            }
+            if (!this.enabled || id == "_top") {
+                return false;
+            }
+            if (id) {
+                const frameElement = getFrameElementById(id);
+                if (frameElement) {
+                    return !frameElement.disabled;
+                }
+            }
+            if (!session.elementDriveEnabled(element)) {
+                return false;
+            }
+            if (submitter && !session.elementDriveEnabled(submitter)) {
+                return false;
+            }
+            return true;
+        }
+        get id() {
+            return this.element.id;
+        }
+        get enabled() {
+            return !this.element.disabled;
+        }
+        get sourceURL() {
+            if (this.element.src) {
+                return this.element.src;
+            }
+        }
+        get reloadable() {
+            const frame = this.findFrameElement(this.element);
+            return frame.hasAttribute("reloadable");
+        }
+        set reloadable(value) {
+            const frame = this.findFrameElement(this.element);
+            if (value) {
+                frame.setAttribute("reloadable", "");
+            }
+            else {
+                frame.removeAttribute("reloadable");
+            }
+        }
+        set sourceURL(sourceURL) {
+            this.settingSourceURL = true;
+            this.element.src = sourceURL !== null && sourceURL !== void 0 ? sourceURL : null;
+            this.currentURL = this.element.src;
+            this.settingSourceURL = false;
+        }
+        get loadingStyle() {
+            return this.element.loading;
+        }
+        get isLoading() {
+            return this.formSubmission !== undefined || this.resolveVisitPromise() !== undefined;
+        }
+        get isActive() {
+            return this.element.isActive && this.connected;
+        }
+        get rootLocation() {
+            var _a;
+            const meta = this.element.ownerDocument.querySelector(`meta[name="turbo-root"]`);
+            const root = (_a = meta === null || meta === void 0 ? void 0 : meta.content) !== null && _a !== void 0 ? _a : "/";
+            return expandURL(root);
+        }
+    }
+    class SnapshotSubstitution {
+        constructor(element) {
+            this.visitCachedSnapshot = ({ element }) => {
+                var _a;
+                const { id, clone } = this;
+                (_a = element.querySelector("#" + id)) === null || _a === void 0 ? void 0 : _a.replaceWith(clone);
+            };
+            this.clone = element.cloneNode(true);
+            this.id = element.id;
+        }
+    }
+    function getFrameElementById(id) {
+        if (id != null) {
+            const element = document.getElementById(id);
+            if (element instanceof FrameElement) {
+                return element;
+            }
+        }
+    }
+    function activateElement(element, currentURL) {
+        if (element) {
+            const src = element.getAttribute("src");
+            if (src != null && currentURL != null && urlsAreEqual(src, currentURL)) {
+                throw new Error(`Matching <turbo-frame id="${element.id}"> element has a source URL which references itself`);
+            }
+            if (element.ownerDocument !== document) {
+                element = document.importNode(element, true);
+            }
+            if (element instanceof FrameElement) {
+                element.connectedCallback();
+                element.disconnectedCallback();
+                return element;
+            }
+        }
+    }
+
+    const StreamActions = {
+        after() {
+            this.targetElements.forEach(e => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e.nextSibling); });
+        },
+        append() {
+            this.removeDuplicateTargetChildren();
+            this.targetElements.forEach(e => e.append(this.templateContent));
+        },
+        before() {
+            this.targetElements.forEach(e => { var _a; return (_a = e.parentElement) === null || _a === void 0 ? void 0 : _a.insertBefore(this.templateContent, e); });
+        },
+        prepend() {
+            this.removeDuplicateTargetChildren();
+            this.targetElements.forEach(e => e.prepend(this.templateContent));
+        },
+        remove() {
+            this.targetElements.forEach(e => e.remove());
+        },
+        replace() {
+            this.targetElements.forEach(e => e.replaceWith(this.templateContent));
+        },
+        update() {
+            this.targetElements.forEach(e => {
+                e.innerHTML = "";
+                e.append(this.templateContent);
+            });
+        }
+    };
+
+    class StreamElement extends HTMLElement {
+        async connectedCallback() {
+            try {
+                await this.render();
+            }
+            catch (error) {
+                console.error(error);
+            }
+            finally {
+                this.disconnect();
+            }
+        }
+        async render() {
+            var _a;
+            return (_a = this.renderPromise) !== null && _a !== void 0 ? _a : (this.renderPromise = (async () => {
+                if (this.dispatchEvent(this.beforeRenderEvent)) {
+                    await nextAnimationFrame();
+                    this.performAction();
+                }
+            })());
+        }
+        disconnect() {
+            try {
+                this.remove();
+            }
+            catch (_a) { }
+        }
+        removeDuplicateTargetChildren() {
+            this.duplicateChildren.forEach(c => c.remove());
+        }
+        get duplicateChildren() {
+            var _a;
+            const existingChildren = this.targetElements.flatMap(e => [...e.children]).filter(c => !!c.id);
+            const newChildrenIds = [...(_a = this.templateContent) === null || _a === void 0 ? void 0 : _a.children].filter(c => !!c.id).map(c => c.id);
+            return existingChildren.filter(c => newChildrenIds.includes(c.id));
+        }
+        get performAction() {
+            if (this.action) {
+                const actionFunction = StreamActions[this.action];
+                if (actionFunction) {
+                    return actionFunction;
+                }
+                this.raise("unknown action");
+            }
+            this.raise("action attribute is missing");
+        }
+        get targetElements() {
+            if (this.target) {
+                return this.targetElementsById;
+            }
+            else if (this.targets) {
+                return this.targetElementsByQuery;
+            }
+            else {
+                this.raise("target or targets attribute is missing");
+            }
+        }
+        get templateContent() {
+            return this.templateElement.content.cloneNode(true);
+        }
+        get templateElement() {
+            if (this.firstElementChild instanceof HTMLTemplateElement) {
+                return this.firstElementChild;
+            }
+            this.raise("first child element must be a <template> element");
+        }
+        get action() {
+            return this.getAttribute("action");
+        }
+        get target() {
+            return this.getAttribute("target");
+        }
+        get targets() {
+            return this.getAttribute("targets");
+        }
+        raise(message) {
+            throw new Error(`${this.description}: ${message}`);
+        }
+        get description() {
+            var _a, _b;
+            return (_b = ((_a = this.outerHTML.match(/<[^>]+>/)) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : "<turbo-stream>";
+        }
+        get beforeRenderEvent() {
+            return new CustomEvent("turbo:before-stream-render", { bubbles: true, cancelable: true });
+        }
+        get targetElementsById() {
+            var _a;
+            const element = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.getElementById(this.target);
+            if (element !== null) {
+                return [element];
+            }
+            else {
+                return [];
+            }
+        }
+        get targetElementsByQuery() {
+            var _a;
+            const elements = (_a = this.ownerDocument) === null || _a === void 0 ? void 0 : _a.querySelectorAll(this.targets);
+            if (elements.length !== 0) {
+                return Array.prototype.slice.call(elements);
+            }
+            else {
+                return [];
+            }
+        }
+    }
+
+    FrameElement.delegateConstructor = FrameController;
+    customElements.define("turbo-frame", FrameElement);
+    customElements.define("turbo-stream", StreamElement);
+
+    (() => {
+        let element = document.currentScript;
+        if (!element)
+            return;
+        if (element.hasAttribute("data-turbo-suppress-warning"))
+            return;
+        while (element = element.parentElement) {
+            if (element == document.body) {
+                return console.warn(unindent `
+        You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
+
+        Load your application’s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.
+
+        For more information, see: https://turbo.hotwired.dev/handbook/building#working-with-script-elements
+
+        ——
+        Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
+      `, element.outerHTML);
+            }
+        }
+    })();
+
+    window.Turbo = Turbo;
+    start();
+
+    exports.PageRenderer = PageRenderer;
+    exports.PageSnapshot = PageSnapshot;
+    exports.clearCache = clearCache;
+    exports.connectStreamSource = connectStreamSource;
+    exports.disconnectStreamSource = disconnectStreamSource;
+    exports.navigator = navigator$1;
+    exports.registerAdapter = registerAdapter;
+    exports.renderStreamMessage = renderStreamMessage;
+    exports.session = session;
+    exports.setConfirmMethod = setConfirmMethod;
+    exports.setProgressBarDelay = setProgressBarDelay;
+    exports.start = start;
+    exports.visit = visit;
+
+    Object.defineProperty(exports, '__esModule', { value: true });
+
+})));
commit 22b70e836069ef492a63fc97baf8e890364492f3
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Thu Jul 28 11:39:25 2022 -0400

    Initial checkin

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8c15e01
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+blib*
+Makefile
+Makefile.old
+pm_to_blib*
+*.tar.gz
+.lwpcookies
+cover_db
+pod2htm*.tmp
+/RT-Extension-Turbo*
+*.bak
+*.swp
+/MYMETA.*
+/t/tmp
+/xt/tmp
diff --git a/Changes b/Changes
new file mode 100644
index 0000000..1ce6aae
--- /dev/null
+++ b/Changes
@@ -0,0 +1,4 @@
+Revision history for RT-Extension-Turbo
+
+0.01 [Release Date]
+ - Initial version
diff --git a/META.yml b/META.yml
new file mode 100644
index 0000000..018d6e1
--- /dev/null
+++ b/META.yml
@@ -0,0 +1,29 @@
+---
+abstract: 'RT-Extension-Turbo Extension'
+author:
+  - 'Best Practical Solutions, LLC <modules at bestpractical.com>'
+build_requires:
+  ExtUtils::MakeMaker: 6.59
+configure_requires:
+  ExtUtils::MakeMaker: 6.59
+distribution_type: module
+dynamic_config: 1
+generated_by: 'Module::Install version 1.19'
+license: gpl_2
+meta-spec:
+  url: http://module-build.sourceforge.net/META-spec-v1.4.html
+  version: 1.4
+name: RT-Extension-Turbo
+no_index:
+  directory:
+    - inc
+    - static
+requires:
+  perl: 5.10.1
+resources:
+  license: http://opensource.org/licenses/gpl-license.php
+  repository: https://github.com/bestpractical/rt-extension-turbo
+version: '0.01'
+x_module_install_rtx_version: '0.43'
+x_requires_rt: 5.0.0
+x_rt_too_new: 6.0.0
diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644
index 0000000..45ca53d
--- /dev/null
+++ b/Makefile.PL
@@ -0,0 +1,12 @@
+use lib '.';
+use inc::Module::Install;
+
+RTx     'RT-Extension-Turbo';
+license 'gpl_2';
+repository 'https://github.com/bestpractical/rt-extension-turbo';
+
+requires_rt '5.0.0';
+rt_too_new '6.0.0';
+
+sign;
+WriteAll;
diff --git a/README b/README
new file mode 100644
index 0000000..78fbf99
--- /dev/null
+++ b/README
@@ -0,0 +1,40 @@
+NAME
+    RT-Extension-Turbo - Experimental extension using Turbo
+
+DESCRIPTION
+    An experimental extension using Turbo <https://turbo.hotwired.dev/> with
+    RT.
+
+RT VERSION
+    Works with RT 5.
+
+INSTALLATION
+    perl Makefile.PL
+    make
+    make install
+        May need root permissions
+
+    Edit your /opt/rt4/etc/RT_SiteConfig.pm
+        Add this line:
+
+            Plugin('RT::Extension::Turbo');
+
+    Clear your mason cache
+            rm -rf /opt/rt4/var/mason_data/obj
+
+    Restart your webserver
+
+AUTHOR
+    Best Practical Solutions, LLC <modules at bestpractical.com>
+
+    All bugs should be reported via email to
+        bug-RT-Extension-Turbo at rt.cpan.org
+    or via the web at
+        http://rt.cpan.org/Public/Dist/Display.html?Name=RT-Extension-Turbo
+LICENSE AND COPYRIGHT
+    This software is Copyright (c) 2022 by Best Practical Solutions, LLC
+
+    This is free software, licensed under:
+
+      The GNU General Public License, Version 2, June 1991
+
diff --git a/lib/RT/Extension/Turbo.pm b/lib/RT/Extension/Turbo.pm
new file mode 100644
index 0000000..5a7e693
--- /dev/null
+++ b/lib/RT/Extension/Turbo.pm
@@ -0,0 +1,70 @@
+use strict;
+use warnings;
+package RT::Extension::Turbo;
+
+our $VERSION = '0.01';
+
+=head1 NAME
+
+RT-Extension-Turbo - Experimental extension using Turbo
+
+=head1 DESCRIPTION
+
+An experimental extension using L<Turbo|https://turbo.hotwired.dev/> with RT.
+
+=head1 RT VERSION
+
+Works with RT 5.
+
+=head1 INSTALLATION
+
+=over
+
+=item C<perl Makefile.PL>
+
+=item C<make>
+
+=item C<make install>
+
+May need root permissions
+
+=item Edit your F</opt/rt4/etc/RT_SiteConfig.pm>
+
+Add this line:
+
+    Plugin('RT::Extension::Turbo');
+
+=item Clear your mason cache
+
+    rm -rf /opt/rt4/var/mason_data/obj
+
+=item Restart your webserver
+
+=back
+
+=head1 AUTHOR
+
+Best Practical Solutions, LLC E<lt>modules at bestpractical.comE<gt>
+
+=for html <p>All bugs should be reported via email to <a
+href="mailto:bug-RT-Extension-Turbo at rt.cpan.org">bug-RT-Extension-Turbo at rt.cpan.org</a>
+or via the web at <a
+href="http://rt.cpan.org/Public/Dist/Display.html?Name=RT-Extension-Turbo">rt.cpan.org</a>.</p>
+
+=for text
+    All bugs should be reported via email to
+        bug-RT-Extension-Turbo at rt.cpan.org
+    or via the web at
+        http://rt.cpan.org/Public/Dist/Display.html?Name=RT-Extension-Turbo
+
+=head1 LICENSE AND COPYRIGHT
+
+This software is Copyright (c) 2022 by Best Practical Solutions, LLC
+
+This is free software, licensed under:
+
+  The GNU General Public License, Version 2, June 1991
+
+=cut
+
+1;
commit 1ab2facb56a37c219564844e188372310613b21d
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Thu Jul 28 11:38:13 2022 -0400

    Module::Install files

diff --git a/inc/Module/Install.pm b/inc/Module/Install.pm
new file mode 100644
index 0000000..7ba98c2
--- /dev/null
+++ b/inc/Module/Install.pm
@@ -0,0 +1,451 @@
+#line 1
+package Module::Install;
+
+# For any maintainers:
+# The load order for Module::Install is a bit magic.
+# It goes something like this...
+#
+# IF ( host has Module::Install installed, creating author mode ) {
+#     1. Makefile.PL calls "use inc::Module::Install"
+#     2. $INC{inc/Module/Install.pm} set to installed version of inc::Module::Install
+#     3. The installed version of inc::Module::Install loads
+#     4. inc::Module::Install calls "require Module::Install"
+#     5. The ./inc/ version of Module::Install loads
+# } ELSE {
+#     1. Makefile.PL calls "use inc::Module::Install"
+#     2. $INC{inc/Module/Install.pm} set to ./inc/ version of Module::Install
+#     3. The ./inc/ version of Module::Install loads
+# }
+
+use 5.006;
+use strict 'vars';
+use Cwd        ();
+use File::Find ();
+use File::Path ();
+
+use vars qw{$VERSION $MAIN};
+BEGIN {
+	# All Module::Install core packages now require synchronised versions.
+	# This will be used to ensure we don't accidentally load old or
+	# different versions of modules.
+	# This is not enforced yet, but will be some time in the next few
+	# releases once we can make sure it won't clash with custom
+	# Module::Install extensions.
+	$VERSION = '1.19';
+
+	# Storage for the pseudo-singleton
+	$MAIN    = undef;
+
+	*inc::Module::Install::VERSION = *VERSION;
+	@inc::Module::Install::ISA     = __PACKAGE__;
+
+}
+
+sub import {
+	my $class = shift;
+	my $self  = $class->new(@_);
+	my $who   = $self->_caller;
+
+	#-------------------------------------------------------------
+	# all of the following checks should be included in import(),
+	# to allow "eval 'require Module::Install; 1' to test
+	# installation of Module::Install. (RT #51267)
+	#-------------------------------------------------------------
+
+	# Whether or not inc::Module::Install is actually loaded, the
+	# $INC{inc/Module/Install.pm} is what will still get set as long as
+	# the caller loaded module this in the documented manner.
+	# If not set, the caller may NOT have loaded the bundled version, and thus
+	# they may not have a MI version that works with the Makefile.PL. This would
+	# result in false errors or unexpected behaviour. And we don't want that.
+	my $file = join( '/', 'inc', split /::/, __PACKAGE__ ) . '.pm';
+	unless ( $INC{$file} ) { die <<"END_DIE" }
+
+Please invoke ${\__PACKAGE__} with:
+
+	use inc::${\__PACKAGE__};
+
+not:
+
+	use ${\__PACKAGE__};
+
+END_DIE
+
+	# This reportedly fixes a rare Win32 UTC file time issue, but
+	# as this is a non-cross-platform XS module not in the core,
+	# we shouldn't really depend on it. See RT #24194 for detail.
+	# (Also, this module only supports Perl 5.6 and above).
+	eval "use Win32::UTCFileTime" if $^O eq 'MSWin32' && $] >= 5.006;
+
+	# If the script that is loading Module::Install is from the future,
+	# then make will detect this and cause it to re-run over and over
+	# again. This is bad. Rather than taking action to touch it (which
+	# is unreliable on some platforms and requires write permissions)
+	# for now we should catch this and refuse to run.
+	if ( -f $0 ) {
+		my $s = (stat($0))[9];
+
+		# If the modification time is only slightly in the future,
+		# sleep briefly to remove the problem.
+		my $a = $s - time;
+		if ( $a > 0 and $a < 5 ) { sleep 5 }
+
+		# Too far in the future, throw an error.
+		my $t = time;
+		if ( $s > $t ) { die <<"END_DIE" }
+
+Your installer $0 has a modification time in the future ($s > $t).
+
+This is known to create infinite loops in make.
+
+Please correct this, then run $0 again.
+
+END_DIE
+	}
+
+
+	# Build.PL was formerly supported, but no longer is due to excessive
+	# difficulty in implementing every single feature twice.
+	if ( $0 =~ /Build.PL$/i ) { die <<"END_DIE" }
+
+Module::Install no longer supports Build.PL.
+
+It was impossible to maintain duel backends, and has been deprecated.
+
+Please remove all Build.PL files and only use the Makefile.PL installer.
+
+END_DIE
+
+	#-------------------------------------------------------------
+
+	# To save some more typing in Module::Install installers, every...
+	# use inc::Module::Install
+	# ...also acts as an implicit use strict.
+	$^H |= strict::bits(qw(refs subs vars));
+
+	#-------------------------------------------------------------
+
+	unless ( -f $self->{file} ) {
+		foreach my $key (keys %INC) {
+			delete $INC{$key} if $key =~ /Module\/Install/;
+		}
+
+		local $^W;
+		require "$self->{path}/$self->{dispatch}.pm";
+		File::Path::mkpath("$self->{prefix}/$self->{author}");
+		$self->{admin} = "$self->{name}::$self->{dispatch}"->new( _top => $self );
+		$self->{admin}->init;
+		@_ = ($class, _self => $self);
+		goto &{"$self->{name}::import"};
+	}
+
+	local $^W;
+	*{"${who}::AUTOLOAD"} = $self->autoload;
+	$self->preload;
+
+	# Unregister loader and worker packages so subdirs can use them again
+	delete $INC{'inc/Module/Install.pm'};
+	delete $INC{'Module/Install.pm'};
+
+	# Save to the singleton
+	$MAIN = $self;
+
+	return 1;
+}
+
+sub autoload {
+	my $self = shift;
+	my $who  = $self->_caller;
+	my $cwd  = Cwd::getcwd();
+	my $sym  = "${who}::AUTOLOAD";
+	$sym->{$cwd} = sub {
+		my $pwd = Cwd::getcwd();
+		if ( my $code = $sym->{$pwd} ) {
+			# Delegate back to parent dirs
+			goto &$code unless $cwd eq $pwd;
+		}
+		unless ($$sym =~ s/([^:]+)$//) {
+			# XXX: it looks like we can't retrieve the missing function
+			# via $$sym (usually $main::AUTOLOAD) in this case.
+			# I'm still wondering if we should slurp Makefile.PL to
+			# get some context or not ...
+			my ($package, $file, $line) = caller;
+			die <<"EOT";
+Unknown function is found at $file line $line.
+Execution of $file aborted due to runtime errors.
+
+If you're a contributor to a project, you may need to install
+some Module::Install extensions from CPAN (or other repository).
+If you're a user of a module, please contact the author.
+EOT
+		}
+		my $method = $1;
+		if ( uc($method) eq $method ) {
+			# Do nothing
+			return;
+		} elsif ( $method =~ /^_/ and $self->can($method) ) {
+			# Dispatch to the root M:I class
+			return $self->$method(@_);
+		}
+
+		# Dispatch to the appropriate plugin
+		unshift @_, ( $self, $1 );
+		goto &{$self->can('call')};
+	};
+}
+
+sub preload {
+	my $self = shift;
+	unless ( $self->{extensions} ) {
+		$self->load_extensions(
+			"$self->{prefix}/$self->{path}", $self
+		);
+	}
+
+	my @exts = @{$self->{extensions}};
+	unless ( @exts ) {
+		@exts = $self->{admin}->load_all_extensions;
+	}
+
+	my %seen;
+	foreach my $obj ( @exts ) {
+		while (my ($method, $glob) = each %{ref($obj) . '::'}) {
+			next unless $obj->can($method);
+			next if $method =~ /^_/;
+			next if $method eq uc($method);
+			$seen{$method}++;
+		}
+	}
+
+	my $who = $self->_caller;
+	foreach my $name ( sort keys %seen ) {
+		local $^W;
+		*{"${who}::$name"} = sub {
+			${"${who}::AUTOLOAD"} = "${who}::$name";
+			goto &{"${who}::AUTOLOAD"};
+		};
+	}
+}
+
+sub new {
+	my ($class, %args) = @_;
+
+	delete $INC{'FindBin.pm'};
+	{
+		# to suppress the redefine warning
+		local $SIG{__WARN__} = sub {};
+		require FindBin;
+	}
+
+	# ignore the prefix on extension modules built from top level.
+	my $base_path = Cwd::abs_path($FindBin::Bin);
+	unless ( Cwd::abs_path(Cwd::getcwd()) eq $base_path ) {
+		delete $args{prefix};
+	}
+	return $args{_self} if $args{_self};
+
+	$base_path = VMS::Filespec::unixify($base_path) if $^O eq 'VMS';
+
+	$args{dispatch} ||= 'Admin';
+	$args{prefix}   ||= 'inc';
+	$args{author}   ||= ($^O eq 'VMS' ? '_author' : '.author');
+	$args{bundle}   ||= 'inc/BUNDLES';
+	$args{base}     ||= $base_path;
+	$class =~ s/^\Q$args{prefix}\E:://;
+	$args{name}     ||= $class;
+	$args{version}  ||= $class->VERSION;
+	unless ( $args{path} ) {
+		$args{path}  = $args{name};
+		$args{path}  =~ s!::!/!g;
+	}
+	$args{file}     ||= "$args{base}/$args{prefix}/$args{path}.pm";
+	$args{wrote}      = 0;
+
+	bless( \%args, $class );
+}
+
+sub call {
+	my ($self, $method) = @_;
+	my $obj = $self->load($method) or return;
+        splice(@_, 0, 2, $obj);
+	goto &{$obj->can($method)};
+}
+
+sub load {
+	my ($self, $method) = @_;
+
+	$self->load_extensions(
+		"$self->{prefix}/$self->{path}", $self
+	) unless $self->{extensions};
+
+	foreach my $obj (@{$self->{extensions}}) {
+		return $obj if $obj->can($method);
+	}
+
+	my $admin = $self->{admin} or die <<"END_DIE";
+The '$method' method does not exist in the '$self->{prefix}' path!
+Please remove the '$self->{prefix}' directory and run $0 again to load it.
+END_DIE
+
+	my $obj = $admin->load($method, 1);
+	push @{$self->{extensions}}, $obj;
+
+	$obj;
+}
+
+sub load_extensions {
+	my ($self, $path, $top) = @_;
+
+	my $should_reload = 0;
+	unless ( grep { ! ref $_ and lc $_ eq lc $self->{prefix} } @INC ) {
+		unshift @INC, $self->{prefix};
+		$should_reload = 1;
+	}
+
+	foreach my $rv ( $self->find_extensions($path) ) {
+		my ($file, $pkg) = @{$rv};
+		next if $self->{pathnames}{$pkg};
+
+		local $@;
+		my $new = eval { local $^W; require $file; $pkg->can('new') };
+		unless ( $new ) {
+			warn $@ if $@;
+			next;
+		}
+		$self->{pathnames}{$pkg} =
+			$should_reload ? delete $INC{$file} : $INC{$file};
+		push @{$self->{extensions}}, &{$new}($pkg, _top => $top );
+	}
+
+	$self->{extensions} ||= [];
+}
+
+sub find_extensions {
+	my ($self, $path) = @_;
+
+	my @found;
+	File::Find::find( {no_chdir => 1, wanted => sub {
+		my $file = $File::Find::name;
+		return unless $file =~ m!^\Q$path\E/(.+)\.pm\Z!is;
+		my $subpath = $1;
+		return if lc($subpath) eq lc($self->{dispatch});
+
+		$file = "$self->{path}/$subpath.pm";
+		my $pkg = "$self->{name}::$subpath";
+		$pkg =~ s!/!::!g;
+
+		# If we have a mixed-case package name, assume case has been preserved
+		# correctly.  Otherwise, root through the file to locate the case-preserved
+		# version of the package name.
+		if ( $subpath eq lc($subpath) || $subpath eq uc($subpath) ) {
+			my $content = Module::Install::_read($File::Find::name);
+			my $in_pod  = 0;
+			foreach ( split /\n/, $content ) {
+				$in_pod = 1 if /^=\w/;
+				$in_pod = 0 if /^=cut/;
+				next if ($in_pod || /^=cut/);  # skip pod text
+				next if /^\s*#/;               # and comments
+				if ( m/^\s*package\s+($pkg)\s*;/i ) {
+					$pkg = $1;
+					last;
+				}
+			}
+		}
+
+		push @found, [ $file, $pkg ];
+	}}, $path ) if -d $path;
+
+	@found;
+}
+
+
+
+
+
+#####################################################################
+# Common Utility Functions
+
+sub _caller {
+	my $depth = 0;
+	my $call  = caller($depth);
+	while ( $call eq __PACKAGE__ ) {
+		$depth++;
+		$call = caller($depth);
+	}
+	return $call;
+}
+
+sub _read {
+	local *FH;
+	open( FH, '<', $_[0] ) or die "open($_[0]): $!";
+	binmode FH;
+	my $string = do { local $/; <FH> };
+	close FH or die "close($_[0]): $!";
+	return $string;
+}
+
+sub _readperl {
+	my $string = Module::Install::_read($_[0]);
+	$string =~ s/(?:\015{1,2}\012|\015|\012)/\n/sg;
+	$string =~ s/(\n)\n*__(?:DATA|END)__\b.*\z/$1/s;
+	$string =~ s/\n\n=\w+.+?\n\n=cut\b.+?\n+/\n\n/sg;
+	return $string;
+}
+
+sub _readpod {
+	my $string = Module::Install::_read($_[0]);
+	$string =~ s/(?:\015{1,2}\012|\015|\012)/\n/sg;
+	return $string if $_[0] =~ /\.pod\z/;
+	$string =~ s/(^|\n=cut\b.+?\n+)[^=\s].+?\n(\n=\w+|\z)/$1$2/sg;
+	$string =~ s/\n*=pod\b[^\n]*\n+/\n\n/sg;
+	$string =~ s/\n*=cut\b[^\n]*\n+/\n\n/sg;
+	$string =~ s/^\n+//s;
+	return $string;
+}
+
+sub _write {
+	local *FH;
+	open( FH, '>', $_[0] ) or die "open($_[0]): $!";
+	binmode FH;
+	foreach ( 1 .. $#_ ) {
+		print FH $_[$_] or die "print($_[0]): $!";
+	}
+	close FH or die "close($_[0]): $!";
+}
+
+# _version is for processing module versions (eg, 1.03_05) not
+# Perl versions (eg, 5.8.1).
+sub _version {
+	my $s = shift || 0;
+	my $d =()= $s =~ /(\.)/g;
+	if ( $d >= 2 ) {
+		# Normalise multipart versions
+		$s =~ s/(\.)(\d{1,3})/sprintf("$1%03d",$2)/eg;
+	}
+	$s =~ s/^(\d+)\.?//;
+	my $l = $1 || 0;
+	my @v = map {
+		$_ . '0' x (3 - length $_)
+	} $s =~ /(\d{1,3})\D?/g;
+	$l = $l . '.' . join '', @v if @v;
+	return $l + 0;
+}
+
+sub _cmp {
+	_version($_[1]) <=> _version($_[2]);
+}
+
+# Cloned from Params::Util::_CLASS
+sub _CLASS {
+	(
+		defined $_[0]
+		and
+		! ref $_[0]
+		and
+		$_[0] =~ m/^[^\W\d]\w*(?:::\w+)*\z/s
+	) ? $_[0] : undef;
+}
+
+1;
+
+# Copyright 2008 - 2012 Adam Kennedy.
diff --git a/inc/Module/Install/Base.pm b/inc/Module/Install/Base.pm
new file mode 100644
index 0000000..9fa42c2
--- /dev/null
+++ b/inc/Module/Install/Base.pm
@@ -0,0 +1,83 @@
+#line 1
+package Module::Install::Base;
+
+use strict 'vars';
+use vars qw{$VERSION};
+BEGIN {
+	$VERSION = '1.19';
+}
+
+# Suspend handler for "redefined" warnings
+BEGIN {
+	my $w = $SIG{__WARN__};
+	$SIG{__WARN__} = sub { $w };
+}
+
+#line 42
+
+sub new {
+	my $class = shift;
+	unless ( defined &{"${class}::call"} ) {
+		*{"${class}::call"} = sub { shift->_top->call(@_) };
+	}
+	unless ( defined &{"${class}::load"} ) {
+		*{"${class}::load"} = sub { shift->_top->load(@_) };
+	}
+	bless { @_ }, $class;
+}
+
+#line 61
+
+sub AUTOLOAD {
+	local $@;
+	my $func = eval { shift->_top->autoload } or return;
+	goto &$func;
+}
+
+#line 75
+
+sub _top {
+	$_[0]->{_top};
+}
+
+#line 90
+
+sub admin {
+	$_[0]->_top->{admin}
+	or
+	Module::Install::Base::FakeAdmin->new;
+}
+
+#line 106
+
+sub is_admin {
+	! $_[0]->admin->isa('Module::Install::Base::FakeAdmin');
+}
+
+sub DESTROY {}
+
+package Module::Install::Base::FakeAdmin;
+
+use vars qw{$VERSION};
+BEGIN {
+	$VERSION = $Module::Install::Base::VERSION;
+}
+
+my $fake;
+
+sub new {
+	$fake ||= bless(\@_, $_[0]);
+}
+
+sub AUTOLOAD {}
+
+sub DESTROY {}
+
+# Restore warning handler
+BEGIN {
+	$SIG{__WARN__} = $SIG{__WARN__}->();
+}
+
+1;
+
+#line 159
diff --git a/inc/Module/Install/Can.pm b/inc/Module/Install/Can.pm
new file mode 100644
index 0000000..d65c753
--- /dev/null
+++ b/inc/Module/Install/Can.pm
@@ -0,0 +1,163 @@
+#line 1
+package Module::Install::Can;
+
+use strict;
+use Config                ();
+use ExtUtils::MakeMaker   ();
+use Module::Install::Base ();
+
+use vars qw{$VERSION @ISA $ISCORE};
+BEGIN {
+	$VERSION = '1.19';
+	@ISA     = 'Module::Install::Base';
+	$ISCORE  = 1;
+}
+
+# check if we can load some module
+### Upgrade this to not have to load the module if possible
+sub can_use {
+	my ($self, $mod, $ver) = @_;
+	$mod =~ s{::|\\}{/}g;
+	$mod .= '.pm' unless $mod =~ /\.pm$/i;
+
+	my $pkg = $mod;
+	$pkg =~ s{/}{::}g;
+	$pkg =~ s{\.pm$}{}i;
+
+	local $@;
+	eval { require $mod; $pkg->VERSION($ver || 0); 1 };
+}
+
+# Check if we can run some command
+sub can_run {
+	my ($self, $cmd) = @_;
+
+	my $_cmd = $cmd;
+	return $_cmd if (-x $_cmd or $_cmd = MM->maybe_command($_cmd));
+
+	for my $dir ((split /$Config::Config{path_sep}/, $ENV{PATH}), '.') {
+		next if $dir eq '';
+		require File::Spec;
+		my $abs = File::Spec->catfile($dir, $cmd);
+		return $abs if (-x $abs or $abs = MM->maybe_command($abs));
+	}
+
+	return;
+}
+
+# Can our C compiler environment build XS files
+sub can_xs {
+	my $self = shift;
+
+	# Ensure we have the CBuilder module
+	$self->configure_requires( 'ExtUtils::CBuilder' => 0.27 );
+
+	# Do we have the configure_requires checker?
+	local $@;
+	eval "require ExtUtils::CBuilder;";
+	if ( $@ ) {
+		# They don't obey configure_requires, so it is
+		# someone old and delicate. Try to avoid hurting
+		# them by falling back to an older simpler test.
+		return $self->can_cc();
+	}
+
+	# Do we have a working C compiler
+	my $builder = ExtUtils::CBuilder->new(
+		quiet => 1,
+	);
+	unless ( $builder->have_compiler ) {
+		# No working C compiler
+		return 0;
+	}
+
+	# Write a C file representative of what XS becomes
+	require File::Temp;
+	my ( $FH, $tmpfile ) = File::Temp::tempfile(
+		"compilexs-XXXXX",
+		SUFFIX => '.c',
+	);
+	binmode $FH;
+	print $FH <<'END_C';
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+
+int main(int argc, char **argv) {
+    return 0;
+}
+
+int boot_sanexs() {
+    return 1;
+}
+
+END_C
+	close $FH;
+
+	# Can the C compiler access the same headers XS does
+	my @libs   = ();
+	my $object = undef;
+	eval {
+		local $^W = 0;
+		$object = $builder->compile(
+			source => $tmpfile,
+		);
+		@libs = $builder->link(
+			objects     => $object,
+			module_name => 'sanexs',
+		);
+	};
+	my $result = $@ ? 0 : 1;
+
+	# Clean up all the build files
+	foreach ( $tmpfile, $object, @libs ) {
+		next unless defined $_;
+		1 while unlink;
+	}
+
+	return $result;
+}
+
+# Can we locate a (the) C compiler
+sub can_cc {
+	my $self   = shift;
+
+	if ($^O eq 'VMS') {
+		require ExtUtils::CBuilder;
+		my $builder = ExtUtils::CBuilder->new(
+		quiet => 1,
+		);
+		return $builder->have_compiler;
+	}
+
+	my @chunks = split(/ /, $Config::Config{cc}) or return;
+
+	# $Config{cc} may contain args; try to find out the program part
+	while (@chunks) {
+		return $self->can_run("@chunks") || (pop(@chunks), next);
+	}
+
+	return;
+}
+
+# Fix Cygwin bug on maybe_command();
+if ( $^O eq 'cygwin' ) {
+	require ExtUtils::MM_Cygwin;
+	require ExtUtils::MM_Win32;
+	if ( ! defined(&ExtUtils::MM_Cygwin::maybe_command) ) {
+		*ExtUtils::MM_Cygwin::maybe_command = sub {
+			my ($self, $file) = @_;
+			if ($file =~ m{^/cygdrive/}i and ExtUtils::MM_Win32->can('maybe_command')) {
+				ExtUtils::MM_Win32->maybe_command($file);
+			} else {
+				ExtUtils::MM_Unix->maybe_command($file);
+			}
+		}
+	}
+}
+
+1;
+
+__END__
+
+#line 245
diff --git a/inc/Module/Install/Fetch.pm b/inc/Module/Install/Fetch.pm
new file mode 100644
index 0000000..3072b08
--- /dev/null
+++ b/inc/Module/Install/Fetch.pm
@@ -0,0 +1,93 @@
+#line 1
+package Module::Install::Fetch;
+
+use strict;
+use Module::Install::Base ();
+
+use vars qw{$VERSION @ISA $ISCORE};
+BEGIN {
+	$VERSION = '1.19';
+	@ISA     = 'Module::Install::Base';
+	$ISCORE  = 1;
+}
+
+sub get_file {
+    my ($self, %args) = @_;
+    my ($scheme, $host, $path, $file) =
+        $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
+
+    if ( $scheme eq 'http' and ! eval { require LWP::Simple; 1 } ) {
+        $args{url} = $args{ftp_url}
+            or (warn("LWP support unavailable!\n"), return);
+        ($scheme, $host, $path, $file) =
+            $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
+    }
+
+    $|++;
+    print "Fetching '$file' from $host... ";
+
+    unless (eval { require Socket; Socket::inet_aton($host) }) {
+        warn "'$host' resolve failed!\n";
+        return;
+    }
+
+    return unless $scheme eq 'ftp' or $scheme eq 'http';
+
+    require Cwd;
+    my $dir = Cwd::getcwd();
+    chdir $args{local_dir} or return if exists $args{local_dir};
+
+    if (eval { require LWP::Simple; 1 }) {
+        LWP::Simple::mirror($args{url}, $file);
+    }
+    elsif (eval { require Net::FTP; 1 }) { eval {
+        # use Net::FTP to get past firewall
+        my $ftp = Net::FTP->new($host, Passive => 1, Timeout => 600);
+        $ftp->login("anonymous", 'anonymous at example.com');
+        $ftp->cwd($path);
+        $ftp->binary;
+        $ftp->get($file) or (warn("$!\n"), return);
+        $ftp->quit;
+    } }
+    elsif (my $ftp = $self->can_run('ftp')) { eval {
+        # no Net::FTP, fallback to ftp.exe
+        require FileHandle;
+        my $fh = FileHandle->new;
+
+        local $SIG{CHLD} = 'IGNORE';
+        unless ($fh->open("|$ftp -n")) {
+            warn "Couldn't open ftp: $!\n";
+            chdir $dir; return;
+        }
+
+        my @dialog = split(/\n/, <<"END_FTP");
+open $host
+user anonymous anonymous\@example.com
+cd $path
+binary
+get $file $file
+quit
+END_FTP
+        foreach (@dialog) { $fh->print("$_\n") }
+        $fh->close;
+    } }
+    else {
+        warn "No working 'ftp' program available!\n";
+        chdir $dir; return;
+    }
+
+    unless (-f $file) {
+        warn "Fetching failed: $@\n";
+        chdir $dir; return;
+    }
+
+    return if exists $args{size} and -s $file != $args{size};
+    system($args{run}) if exists $args{run};
+    unlink($file) if $args{remove};
+
+    print(((!exists $args{check_for} or -e $args{check_for})
+        ? "done!" : "failed! ($!)"), "\n");
+    chdir $dir; return !$?;
+}
+
+1;
diff --git a/inc/Module/Install/Include.pm b/inc/Module/Install/Include.pm
new file mode 100644
index 0000000..13fdcd0
--- /dev/null
+++ b/inc/Module/Install/Include.pm
@@ -0,0 +1,34 @@
+#line 1
+package Module::Install::Include;
+
+use strict;
+use Module::Install::Base ();
+
+use vars qw{$VERSION @ISA $ISCORE};
+BEGIN {
+	$VERSION = '1.19';
+	@ISA     = 'Module::Install::Base';
+	$ISCORE  = 1;
+}
+
+sub include {
+	shift()->admin->include(@_);
+}
+
+sub include_deps {
+	shift()->admin->include_deps(@_);
+}
+
+sub auto_include {
+	shift()->admin->auto_include(@_);
+}
+
+sub auto_include_deps {
+	shift()->admin->auto_include_deps(@_);
+}
+
+sub auto_include_dependent_dists {
+	shift()->admin->auto_include_dependent_dists(@_);
+}
+
+1;
diff --git a/inc/Module/Install/Makefile.pm b/inc/Module/Install/Makefile.pm
new file mode 100644
index 0000000..13a4464
--- /dev/null
+++ b/inc/Module/Install/Makefile.pm
@@ -0,0 +1,418 @@
+#line 1
+package Module::Install::Makefile;
+
+use strict 'vars';
+use ExtUtils::MakeMaker   ();
+use Module::Install::Base ();
+use Fcntl qw/:flock :seek/;
+
+use vars qw{$VERSION @ISA $ISCORE};
+BEGIN {
+	$VERSION = '1.19';
+	@ISA     = 'Module::Install::Base';
+	$ISCORE  = 1;
+}
+
+sub Makefile { $_[0] }
+
+my %seen = ();
+
+sub prompt {
+	shift;
+
+	# Infinite loop protection
+	my @c = caller();
+	if ( ++$seen{"$c[1]|$c[2]|$_[0]"} > 3 ) {
+		die "Caught an potential prompt infinite loop ($c[1]|$c[2]|$_[0])";
+	}
+
+	# In automated testing or non-interactive session, always use defaults
+	if ( ($ENV{AUTOMATED_TESTING} or -! -t STDIN) and ! $ENV{PERL_MM_USE_DEFAULT} ) {
+		local $ENV{PERL_MM_USE_DEFAULT} = 1;
+		goto &ExtUtils::MakeMaker::prompt;
+	} else {
+		goto &ExtUtils::MakeMaker::prompt;
+	}
+}
+
+# Store a cleaned up version of the MakeMaker version,
+# since we need to behave differently in a variety of
+# ways based on the MM version.
+my $makemaker = eval $ExtUtils::MakeMaker::VERSION;
+
+# If we are passed a param, do a "newer than" comparison.
+# Otherwise, just return the MakeMaker version.
+sub makemaker {
+	( @_ < 2 or $makemaker >= eval($_[1]) ) ? $makemaker : 0
+}
+
+# Ripped from ExtUtils::MakeMaker 6.56, and slightly modified
+# as we only need to know here whether the attribute is an array
+# or a hash or something else (which may or may not be appendable).
+my %makemaker_argtype = (
+ C                  => 'ARRAY',
+ CONFIG             => 'ARRAY',
+# CONFIGURE          => 'CODE', # ignore
+ DIR                => 'ARRAY',
+ DL_FUNCS           => 'HASH',
+ DL_VARS            => 'ARRAY',
+ EXCLUDE_EXT        => 'ARRAY',
+ EXE_FILES          => 'ARRAY',
+ FUNCLIST           => 'ARRAY',
+ H                  => 'ARRAY',
+ IMPORTS            => 'HASH',
+ INCLUDE_EXT        => 'ARRAY',
+ LIBS               => 'ARRAY', # ignore ''
+ MAN1PODS           => 'HASH',
+ MAN3PODS           => 'HASH',
+ META_ADD           => 'HASH',
+ META_MERGE         => 'HASH',
+ PL_FILES           => 'HASH',
+ PM                 => 'HASH',
+ PMLIBDIRS          => 'ARRAY',
+ PMLIBPARENTDIRS    => 'ARRAY',
+ PREREQ_PM          => 'HASH',
+ CONFIGURE_REQUIRES => 'HASH',
+ SKIP               => 'ARRAY',
+ TYPEMAPS           => 'ARRAY',
+ XS                 => 'HASH',
+# VERSION            => ['version',''],  # ignore
+# _KEEP_AFTER_FLUSH  => '',
+
+ clean      => 'HASH',
+ depend     => 'HASH',
+ dist       => 'HASH',
+ dynamic_lib=> 'HASH',
+ linkext    => 'HASH',
+ macro      => 'HASH',
+ postamble  => 'HASH',
+ realclean  => 'HASH',
+ test       => 'HASH',
+ tool_autosplit => 'HASH',
+
+ # special cases where you can use makemaker_append
+ CCFLAGS   => 'APPENDABLE',
+ DEFINE    => 'APPENDABLE',
+ INC       => 'APPENDABLE',
+ LDDLFLAGS => 'APPENDABLE',
+ LDFROM    => 'APPENDABLE',
+);
+
+sub makemaker_args {
+	my ($self, %new_args) = @_;
+	my $args = ( $self->{makemaker_args} ||= {} );
+	foreach my $key (keys %new_args) {
+		if ($makemaker_argtype{$key}) {
+			if ($makemaker_argtype{$key} eq 'ARRAY') {
+				$args->{$key} = [] unless defined $args->{$key};
+				unless (ref $args->{$key} eq 'ARRAY') {
+					$args->{$key} = [$args->{$key}]
+				}
+				push @{$args->{$key}},
+					ref $new_args{$key} eq 'ARRAY'
+						? @{$new_args{$key}}
+						: $new_args{$key};
+			}
+			elsif ($makemaker_argtype{$key} eq 'HASH') {
+				$args->{$key} = {} unless defined $args->{$key};
+				foreach my $skey (keys %{ $new_args{$key} }) {
+					$args->{$key}{$skey} = $new_args{$key}{$skey};
+				}
+			}
+			elsif ($makemaker_argtype{$key} eq 'APPENDABLE') {
+				$self->makemaker_append($key => $new_args{$key});
+			}
+		}
+		else {
+			if (defined $args->{$key}) {
+				warn qq{MakeMaker attribute "$key" is overriden; use "makemaker_append" to append values\n};
+			}
+			$args->{$key} = $new_args{$key};
+		}
+	}
+	return $args;
+}
+
+# For mm args that take multiple space-separated args,
+# append an argument to the current list.
+sub makemaker_append {
+	my $self = shift;
+	my $name = shift;
+	my $args = $self->makemaker_args;
+	$args->{$name} = defined $args->{$name}
+		? join( ' ', $args->{$name}, @_ )
+		: join( ' ', @_ );
+}
+
+sub build_subdirs {
+	my $self    = shift;
+	my $subdirs = $self->makemaker_args->{DIR} ||= [];
+	for my $subdir (@_) {
+		push @$subdirs, $subdir;
+	}
+}
+
+sub clean_files {
+	my $self  = shift;
+	my $clean = $self->makemaker_args->{clean} ||= {};
+	  %$clean = (
+		%$clean,
+		FILES => join ' ', grep { length $_ } ($clean->{FILES} || (), @_),
+	);
+}
+
+sub realclean_files {
+	my $self      = shift;
+	my $realclean = $self->makemaker_args->{realclean} ||= {};
+	  %$realclean = (
+		%$realclean,
+		FILES => join ' ', grep { length $_ } ($realclean->{FILES} || (), @_),
+	);
+}
+
+sub libs {
+	my $self = shift;
+	my $libs = ref $_[0] ? shift : [ shift ];
+	$self->makemaker_args( LIBS => $libs );
+}
+
+sub inc {
+	my $self = shift;
+	$self->makemaker_args( INC => shift );
+}
+
+sub _wanted_t {
+}
+
+sub tests_recursive {
+	my $self = shift;
+	my $dir = shift || 't';
+	unless ( -d $dir ) {
+		die "tests_recursive dir '$dir' does not exist";
+	}
+	my %tests = map { $_ => 1 } split / /, ($self->tests || '');
+	require File::Find;
+	File::Find::find(
+        sub { /\.t$/ and -f $_ and $tests{"$File::Find::dir/*.t"} = 1 },
+        $dir
+    );
+	$self->tests( join ' ', sort keys %tests );
+}
+
+sub write {
+	my $self = shift;
+	die "&Makefile->write() takes no arguments\n" if @_;
+
+	# Check the current Perl version
+	my $perl_version = $self->perl_version;
+	if ( $perl_version ) {
+		eval "use $perl_version; 1"
+			or die "ERROR: perl: Version $] is installed, "
+			. "but we need version >= $perl_version";
+	}
+
+	# Make sure we have a new enough MakeMaker
+	require ExtUtils::MakeMaker;
+
+	if ( $perl_version and $self->_cmp($perl_version, '5.006') >= 0 ) {
+		# This previous attempted to inherit the version of
+		# ExtUtils::MakeMaker in use by the module author, but this
+		# was found to be untenable as some authors build releases
+		# using future dev versions of EU:MM that nobody else has.
+		# Instead, #toolchain suggests we use 6.59 which is the most
+		# stable version on CPAN at time of writing and is, to quote
+		# ribasushi, "not terminally fucked, > and tested enough".
+		# TODO: We will now need to maintain this over time to push
+		# the version up as new versions are released.
+		$self->build_requires(     'ExtUtils::MakeMaker' => 6.59 );
+		$self->configure_requires( 'ExtUtils::MakeMaker' => 6.59 );
+	} else {
+		# Allow legacy-compatibility with 5.005 by depending on the
+		# most recent EU:MM that supported 5.005.
+		$self->build_requires(     'ExtUtils::MakeMaker' => 6.36 );
+		$self->configure_requires( 'ExtUtils::MakeMaker' => 6.36 );
+	}
+
+	# Generate the MakeMaker params
+	my $args = $self->makemaker_args;
+	$args->{DISTNAME} = $self->name;
+	$args->{NAME}     = $self->module_name || $self->name;
+	$args->{NAME}     =~ s/-/::/g;
+	$args->{VERSION}  = $self->version or die <<'EOT';
+ERROR: Can't determine distribution version. Please specify it
+explicitly via 'version' in Makefile.PL, or set a valid $VERSION
+in a module, and provide its file path via 'version_from' (or
+'all_from' if you prefer) in Makefile.PL.
+EOT
+
+	if ( $self->tests ) {
+		my @tests = split ' ', $self->tests;
+		my %seen;
+		$args->{test} = {
+			TESTS => (join ' ', grep {!$seen{$_}++} @tests),
+		};
+    } elsif ( $Module::Install::ExtraTests::use_extratests ) {
+        # Module::Install::ExtraTests doesn't set $self->tests and does its own tests via harness.
+        # So, just ignore our xt tests here.
+	} elsif ( -d 'xt' and ($Module::Install::AUTHOR or $ENV{RELEASE_TESTING}) ) {
+		$args->{test} = {
+			TESTS => join( ' ', map { "$_/*.t" } grep { -d $_ } qw{ t xt } ),
+		};
+	}
+	if ( $] >= 5.005 ) {
+		$args->{ABSTRACT} = $self->abstract;
+		$args->{AUTHOR}   = join ', ', @{$self->author || []};
+	}
+	if ( $self->makemaker(6.10) ) {
+		$args->{NO_META}   = 1;
+		#$args->{NO_MYMETA} = 1;
+	}
+	if ( $self->makemaker(6.17) and $self->sign ) {
+		$args->{SIGN} = 1;
+	}
+	unless ( $self->is_admin ) {
+		delete $args->{SIGN};
+	}
+	if ( $self->makemaker(6.31) and $self->license ) {
+		$args->{LICENSE} = $self->license;
+	}
+
+	my $prereq = ($args->{PREREQ_PM} ||= {});
+	%$prereq = ( %$prereq,
+		map { @$_ } # flatten [module => version]
+		map { @$_ }
+		grep $_,
+		($self->requires)
+	);
+
+	# Remove any reference to perl, PREREQ_PM doesn't support it
+	delete $args->{PREREQ_PM}->{perl};
+
+	# Merge both kinds of requires into BUILD_REQUIRES
+	my $build_prereq = ($args->{BUILD_REQUIRES} ||= {});
+	%$build_prereq = ( %$build_prereq,
+		map { @$_ } # flatten [module => version]
+		map { @$_ }
+		grep $_,
+		($self->configure_requires, $self->build_requires)
+	);
+
+	# Remove any reference to perl, BUILD_REQUIRES doesn't support it
+	delete $args->{BUILD_REQUIRES}->{perl};
+
+	# Delete bundled dists from prereq_pm, add it to Makefile DIR
+	my $subdirs = ($args->{DIR} || []);
+	if ($self->bundles) {
+		my %processed;
+		foreach my $bundle (@{ $self->bundles }) {
+			my ($mod_name, $dist_dir) = @$bundle;
+			delete $prereq->{$mod_name};
+			$dist_dir = File::Basename::basename($dist_dir); # dir for building this module
+			if (not exists $processed{$dist_dir}) {
+				if (-d $dist_dir) {
+					# List as sub-directory to be processed by make
+					push @$subdirs, $dist_dir;
+				}
+				# Else do nothing: the module is already present on the system
+				$processed{$dist_dir} = undef;
+			}
+		}
+	}
+
+	unless ( $self->makemaker('6.55_03') ) {
+		%$prereq = (%$prereq,%$build_prereq);
+		delete $args->{BUILD_REQUIRES};
+	}
+
+	if ( my $perl_version = $self->perl_version ) {
+		eval "use $perl_version; 1"
+			or die "ERROR: perl: Version $] is installed, "
+			. "but we need version >= $perl_version";
+
+		if ( $self->makemaker(6.48) ) {
+			$args->{MIN_PERL_VERSION} = $perl_version;
+		}
+	}
+
+	if ($self->installdirs) {
+		warn qq{old INSTALLDIRS (probably set by makemaker_args) is overriden by installdirs\n} if $args->{INSTALLDIRS};
+		$args->{INSTALLDIRS} = $self->installdirs;
+	}
+
+	my %args = map {
+		( $_ => $args->{$_} ) } grep {defined($args->{$_} )
+	} keys %$args;
+
+	my $user_preop = delete $args{dist}->{PREOP};
+	if ( my $preop = $self->admin->preop($user_preop) ) {
+		foreach my $key ( keys %$preop ) {
+			$args{dist}->{$key} = $preop->{$key};
+		}
+	}
+
+	my $mm = ExtUtils::MakeMaker::WriteMakefile(%args);
+	$self->fix_up_makefile($mm->{FIRST_MAKEFILE} || 'Makefile');
+}
+
+sub fix_up_makefile {
+	my $self          = shift;
+	my $makefile_name = shift;
+	my $top_class     = ref($self->_top) || '';
+	my $top_version   = $self->_top->VERSION || '';
+
+	my $preamble = $self->preamble
+		? "# Preamble by $top_class $top_version\n"
+			. $self->preamble
+		: '';
+	my $postamble = "# Postamble by $top_class $top_version\n"
+		. ($self->postamble || '');
+
+	local *MAKEFILE;
+	open MAKEFILE, "+< $makefile_name" or die "fix_up_makefile: Couldn't open $makefile_name: $!";
+	eval { flock MAKEFILE, LOCK_EX };
+	my $makefile = do { local $/; <MAKEFILE> };
+
+	$makefile =~ s/\b(test_harness\(\$\(TEST_VERBOSE\), )/$1'inc', /;
+	$makefile =~ s/( -I\$\(INST_ARCHLIB\))/ -Iinc$1/g;
+	$makefile =~ s/( "-I\$\(INST_LIB\)")/ "-Iinc"$1/g;
+	$makefile =~ s/^(FULLPERL = .*)/$1 "-Iinc"/m;
+	$makefile =~ s/^(PERL = .*)/$1 "-Iinc"/m;
+
+	# Module::Install will never be used to build the Core Perl
+	# Sometimes PERL_LIB and PERL_ARCHLIB get written anyway, which breaks
+	# PREFIX/PERL5LIB, and thus, install_share. Blank them if they exist
+	$makefile =~ s/^PERL_LIB = .+/PERL_LIB =/m;
+	#$makefile =~ s/^PERL_ARCHLIB = .+/PERL_ARCHLIB =/m;
+
+	# Perl 5.005 mentions PERL_LIB explicitly, so we have to remove that as well.
+	$makefile =~ s/(\"?)-I\$\(PERL_LIB\)\1//g;
+
+	# XXX - This is currently unused; not sure if it breaks other MM-users
+	# $makefile =~ s/^pm_to_blib\s+:\s+/pm_to_blib :: /mg;
+
+	seek MAKEFILE, 0, SEEK_SET;
+	truncate MAKEFILE, 0;
+	print MAKEFILE  "$preamble$makefile$postamble" or die $!;
+	close MAKEFILE  or die $!;
+
+	1;
+}
+
+sub preamble {
+	my ($self, $text) = @_;
+	$self->{preamble} = $text . $self->{preamble} if defined $text;
+	$self->{preamble};
+}
+
+sub postamble {
+	my ($self, $text) = @_;
+	$self->{postamble} ||= $self->admin->postamble;
+	$self->{postamble} .= $text if defined $text;
+	$self->{postamble}
+}
+
+1;
+
+__END__
+
+#line 544
diff --git a/inc/Module/Install/Metadata.pm b/inc/Module/Install/Metadata.pm
new file mode 100644
index 0000000..11bf971
--- /dev/null
+++ b/inc/Module/Install/Metadata.pm
@@ -0,0 +1,722 @@
+#line 1
+package Module::Install::Metadata;
+
+use strict 'vars';
+use Module::Install::Base ();
+
+use vars qw{$VERSION @ISA $ISCORE};
+BEGIN {
+	$VERSION = '1.19';
+	@ISA     = 'Module::Install::Base';
+	$ISCORE  = 1;
+}
+
+my @boolean_keys = qw{
+	sign
+};
+
+my @scalar_keys = qw{
+	name
+	module_name
+	abstract
+	version
+	distribution_type
+	tests
+	installdirs
+};
+
+my @tuple_keys = qw{
+	configure_requires
+	build_requires
+	requires
+	recommends
+	bundles
+	resources
+};
+
+my @resource_keys = qw{
+	homepage
+	bugtracker
+	repository
+};
+
+my @array_keys = qw{
+	keywords
+	author
+};
+
+*authors = \&author;
+
+sub Meta              { shift          }
+sub Meta_BooleanKeys  { @boolean_keys  }
+sub Meta_ScalarKeys   { @scalar_keys   }
+sub Meta_TupleKeys    { @tuple_keys    }
+sub Meta_ResourceKeys { @resource_keys }
+sub Meta_ArrayKeys    { @array_keys    }
+
+foreach my $key ( @boolean_keys ) {
+	*$key = sub {
+		my $self = shift;
+		if ( defined wantarray and not @_ ) {
+			return $self->{values}->{$key};
+		}
+		$self->{values}->{$key} = ( @_ ? $_[0] : 1 );
+		return $self;
+	};
+}
+
+foreach my $key ( @scalar_keys ) {
+	*$key = sub {
+		my $self = shift;
+		return $self->{values}->{$key} if defined wantarray and !@_;
+		$self->{values}->{$key} = shift;
+		return $self;
+	};
+}
+
+foreach my $key ( @array_keys ) {
+	*$key = sub {
+		my $self = shift;
+		return $self->{values}->{$key} if defined wantarray and !@_;
+		$self->{values}->{$key} ||= [];
+		push @{$self->{values}->{$key}}, @_;
+		return $self;
+	};
+}
+
+foreach my $key ( @resource_keys ) {
+	*$key = sub {
+		my $self = shift;
+		unless ( @_ ) {
+			return () unless $self->{values}->{resources};
+			return map  { $_->[1] }
+			       grep { $_->[0] eq $key }
+			       @{ $self->{values}->{resources} };
+		}
+		return $self->{values}->{resources}->{$key} unless @_;
+		my $uri = shift or die(
+			"Did not provide a value to $key()"
+		);
+		$self->resources( $key => $uri );
+		return 1;
+	};
+}
+
+foreach my $key ( grep { $_ ne "resources" } @tuple_keys) {
+	*$key = sub {
+		my $self = shift;
+		return $self->{values}->{$key} unless @_;
+		my @added;
+		while ( @_ ) {
+			my $module  = shift or last;
+			my $version = shift || 0;
+			push @added, [ $module, $version ];
+		}
+		push @{ $self->{values}->{$key} }, @added;
+		return map {@$_} @added;
+	};
+}
+
+# Resource handling
+my %lc_resource = map { $_ => 1 } qw{
+	homepage
+	license
+	bugtracker
+	repository
+};
+
+sub resources {
+	my $self = shift;
+	while ( @_ ) {
+		my $name  = shift or last;
+		my $value = shift or next;
+		if ( $name eq lc $name and ! $lc_resource{$name} ) {
+			die("Unsupported reserved lowercase resource '$name'");
+		}
+		$self->{values}->{resources} ||= [];
+		push @{ $self->{values}->{resources} }, [ $name, $value ];
+	}
+	$self->{values}->{resources};
+}
+
+# Aliases for build_requires that will have alternative
+# meanings in some future version of META.yml.
+sub test_requires     { shift->build_requires(@_) }
+sub install_requires  { shift->build_requires(@_) }
+
+# Aliases for installdirs options
+sub install_as_core   { $_[0]->installdirs('perl')   }
+sub install_as_cpan   { $_[0]->installdirs('site')   }
+sub install_as_site   { $_[0]->installdirs('site')   }
+sub install_as_vendor { $_[0]->installdirs('vendor') }
+
+sub dynamic_config {
+	my $self  = shift;
+	my $value = @_ ? shift : 1;
+	if ( $self->{values}->{dynamic_config} ) {
+		# Once dynamic we never change to static, for safety
+		return 0;
+	}
+	$self->{values}->{dynamic_config} = $value ? 1 : 0;
+	return 1;
+}
+
+# Convenience command
+sub static_config {
+	shift->dynamic_config(0);
+}
+
+sub perl_version {
+	my $self = shift;
+	return $self->{values}->{perl_version} unless @_;
+	my $version = shift or die(
+		"Did not provide a value to perl_version()"
+	);
+
+	# Normalize the version
+	$version = $self->_perl_version($version);
+
+	# We don't support the really old versions
+	unless ( $version >= 5.005 ) {
+		die "Module::Install only supports 5.005 or newer (use ExtUtils::MakeMaker)\n";
+	}
+
+	$self->{values}->{perl_version} = $version;
+}
+
+sub all_from {
+	my ( $self, $file ) = @_;
+
+	unless ( defined($file) ) {
+		my $name = $self->name or die(
+			"all_from called with no args without setting name() first"
+		);
+		$file = join('/', 'lib', split(/-/, $name)) . '.pm';
+		$file =~ s{.*/}{} unless -e $file;
+		unless ( -e $file ) {
+			die("all_from cannot find $file from $name");
+		}
+	}
+	unless ( -f $file ) {
+		die("The path '$file' does not exist, or is not a file");
+	}
+
+	$self->{values}{all_from} = $file;
+
+	# Some methods pull from POD instead of code.
+	# If there is a matching .pod, use that instead
+	my $pod = $file;
+	$pod =~ s/\.pm$/.pod/i;
+	$pod = $file unless -e $pod;
+
+	# Pull the different values
+	$self->name_from($file)         unless $self->name;
+	$self->version_from($file)      unless $self->version;
+	$self->perl_version_from($file) unless $self->perl_version;
+	$self->author_from($pod)        unless @{$self->author || []};
+	$self->license_from($pod)       unless $self->license;
+	$self->abstract_from($pod)      unless $self->abstract;
+
+	return 1;
+}
+
+sub provides {
+	my $self     = shift;
+	my $provides = ( $self->{values}->{provides} ||= {} );
+	%$provides = (%$provides, @_) if @_;
+	return $provides;
+}
+
+sub auto_provides {
+	my $self = shift;
+	return $self unless $self->is_admin;
+	unless (-e 'MANIFEST') {
+		warn "Cannot deduce auto_provides without a MANIFEST, skipping\n";
+		return $self;
+	}
+	# Avoid spurious warnings as we are not checking manifest here.
+	local $SIG{__WARN__} = sub {1};
+	require ExtUtils::Manifest;
+	local *ExtUtils::Manifest::manicheck = sub { return };
+
+	require Module::Build;
+	my $build = Module::Build->new(
+		dist_name    => $self->name,
+		dist_version => $self->version,
+		license      => $self->license,
+	);
+	$self->provides( %{ $build->find_dist_packages || {} } );
+}
+
+sub feature {
+	my $self     = shift;
+	my $name     = shift;
+	my $features = ( $self->{values}->{features} ||= [] );
+	my $mods;
+
+	if ( @_ == 1 and ref( $_[0] ) ) {
+		# The user used ->feature like ->features by passing in the second
+		# argument as a reference.  Accomodate for that.
+		$mods = $_[0];
+	} else {
+		$mods = \@_;
+	}
+
+	my $count = 0;
+	push @$features, (
+		$name => [
+			map {
+				ref($_) ? ( ref($_) eq 'HASH' ) ? %$_ : @$_ : $_
+			} @$mods
+		]
+	);
+
+	return @$features;
+}
+
+sub features {
+	my $self = shift;
+	while ( my ( $name, $mods ) = splice( @_, 0, 2 ) ) {
+		$self->feature( $name, @$mods );
+	}
+	return $self->{values}->{features}
+		? @{ $self->{values}->{features} }
+		: ();
+}
+
+sub no_index {
+	my $self = shift;
+	my $type = shift;
+	push @{ $self->{values}->{no_index}->{$type} }, @_ if $type;
+	return $self->{values}->{no_index};
+}
+
+sub read {
+	my $self = shift;
+	$self->include_deps( 'YAML::Tiny', 0 );
+
+	require YAML::Tiny;
+	my $data = YAML::Tiny::LoadFile('META.yml');
+
+	# Call methods explicitly in case user has already set some values.
+	while ( my ( $key, $value ) = each %$data ) {
+		next unless $self->can($key);
+		if ( ref $value eq 'HASH' ) {
+			while ( my ( $module, $version ) = each %$value ) {
+				$self->can($key)->($self, $module => $version );
+			}
+		} else {
+			$self->can($key)->($self, $value);
+		}
+	}
+	return $self;
+}
+
+sub write {
+	my $self = shift;
+	return $self unless $self->is_admin;
+	$self->admin->write_meta;
+	return $self;
+}
+
+sub version_from {
+	require ExtUtils::MM_Unix;
+	my ( $self, $file ) = @_;
+	$self->version( ExtUtils::MM_Unix->parse_version($file) );
+
+	# for version integrity check
+	$self->makemaker_args( VERSION_FROM => $file );
+}
+
+sub abstract_from {
+	require ExtUtils::MM_Unix;
+	my ( $self, $file ) = @_;
+	$self->abstract(
+		bless(
+			{ DISTNAME => $self->name },
+			'ExtUtils::MM_Unix'
+		)->parse_abstract($file)
+	);
+}
+
+# Add both distribution and module name
+sub name_from {
+	my ($self, $file) = @_;
+	if (
+		Module::Install::_read($file) =~ m/
+		^ \s*
+		package \s*
+		([\w:]+)
+		[\s|;]*
+		/ixms
+	) {
+		my ($name, $module_name) = ($1, $1);
+		$name =~ s{::}{-}g;
+		$self->name($name);
+		unless ( $self->module_name ) {
+			$self->module_name($module_name);
+		}
+	} else {
+		die("Cannot determine name from $file\n");
+	}
+}
+
+sub _extract_perl_version {
+	if (
+		$_[0] =~ m/
+		^\s*
+		(?:use|require) \s*
+		v?
+		([\d_\.]+)
+		\s* ;
+		/ixms
+	) {
+		my $perl_version = $1;
+		$perl_version =~ s{_}{}g;
+		return $perl_version;
+	} else {
+		return;
+	}
+}
+
+sub perl_version_from {
+	my $self = shift;
+	my $perl_version=_extract_perl_version(Module::Install::_read($_[0]));
+	if ($perl_version) {
+		$self->perl_version($perl_version);
+	} else {
+		warn "Cannot determine perl version info from $_[0]\n";
+		return;
+	}
+}
+
+sub author_from {
+	my $self    = shift;
+	my $content = Module::Install::_read($_[0]);
+	if ($content =~ m/
+		=head \d \s+ (?:authors?)\b \s*
+		([^\n]*)
+		|
+		=head \d \s+ (?:licen[cs]e|licensing|copyright|legal)\b \s*
+		.*? copyright .*? \d\d\d[\d.]+ \s* (?:\bby\b)? \s*
+		([^\n]*)
+	/ixms) {
+		my $author = $1 || $2;
+
+		# XXX: ugly but should work anyway...
+		if (eval "require Pod::Escapes; 1") {
+			# Pod::Escapes has a mapping table.
+			# It's in core of perl >= 5.9.3, and should be installed
+			# as one of the Pod::Simple's prereqs, which is a prereq
+			# of Pod::Text 3.x (see also below).
+			$author =~ s{ E<( (\d+) | ([A-Za-z]+) )> }
+			{
+				defined $2
+				? chr($2)
+				: defined $Pod::Escapes::Name2character_number{$1}
+				? chr($Pod::Escapes::Name2character_number{$1})
+				: do {
+					warn "Unknown escape: E<$1>";
+					"E<$1>";
+				};
+			}gex;
+		}
+		elsif (eval "require Pod::Text; 1" && $Pod::Text::VERSION < 3) {
+			# Pod::Text < 3.0 has yet another mapping table,
+			# though the table name of 2.x and 1.x are different.
+			# (1.x is in core of Perl < 5.6, 2.x is in core of
+			# Perl < 5.9.3)
+			my $mapping = ($Pod::Text::VERSION < 2)
+				? \%Pod::Text::HTML_Escapes
+				: \%Pod::Text::ESCAPES;
+			$author =~ s{ E<( (\d+) | ([A-Za-z]+) )> }
+			{
+				defined $2
+				? chr($2)
+				: defined $mapping->{$1}
+				? $mapping->{$1}
+				: do {
+					warn "Unknown escape: E<$1>";
+					"E<$1>";
+				};
+			}gex;
+		}
+		else {
+			$author =~ s{E<lt>}{<}g;
+			$author =~ s{E<gt>}{>}g;
+		}
+		$self->author($author);
+	} else {
+		warn "Cannot determine author info from $_[0]\n";
+	}
+}
+
+#Stolen from M::B
+my %license_urls = (
+    perl         => 'http://dev.perl.org/licenses/',
+    apache       => 'http://apache.org/licenses/LICENSE-2.0',
+    apache_1_1   => 'http://apache.org/licenses/LICENSE-1.1',
+    artistic     => 'http://opensource.org/licenses/artistic-license.php',
+    artistic_2   => 'http://opensource.org/licenses/artistic-license-2.0.php',
+    lgpl         => 'http://opensource.org/licenses/lgpl-license.php',
+    lgpl2        => 'http://opensource.org/licenses/lgpl-2.1.php',
+    lgpl3        => 'http://opensource.org/licenses/lgpl-3.0.html',
+    bsd          => 'http://opensource.org/licenses/bsd-license.php',
+    gpl          => 'http://opensource.org/licenses/gpl-license.php',
+    gpl2         => 'http://opensource.org/licenses/gpl-2.0.php',
+    gpl3         => 'http://opensource.org/licenses/gpl-3.0.html',
+    mit          => 'http://opensource.org/licenses/mit-license.php',
+    mozilla      => 'http://opensource.org/licenses/mozilla1.1.php',
+    open_source  => undef,
+    unrestricted => undef,
+    restrictive  => undef,
+    unknown      => undef,
+);
+
+sub license {
+	my $self = shift;
+	return $self->{values}->{license} unless @_;
+	my $license = shift or die(
+		'Did not provide a value to license()'
+	);
+	$license = __extract_license($license) || lc $license;
+	$self->{values}->{license} = $license;
+
+	# Automatically fill in license URLs
+	if ( $license_urls{$license} ) {
+		$self->resources( license => $license_urls{$license} );
+	}
+
+	return 1;
+}
+
+sub _extract_license {
+	my $pod = shift;
+	my $matched;
+	return __extract_license(
+		($matched) = $pod =~ m/
+			(=head \d \s+ L(?i:ICEN[CS]E|ICENSING)\b.*?)
+			(=head \d.*|=cut.*|)\z
+		/xms
+	) || __extract_license(
+		($matched) = $pod =~ m/
+			(=head \d \s+ (?:C(?i:OPYRIGHTS?)|L(?i:EGAL))\b.*?)
+			(=head \d.*|=cut.*|)\z
+		/xms
+	);
+}
+
+sub __extract_license {
+	my $license_text = shift or return;
+	my @phrases      = (
+		'(?:under )?the same (?:terms|license) as (?:perl|the perl (?:\d )?programming language)' => 'perl', 1,
+		'(?:under )?the terms of (?:perl|the perl programming language) itself' => 'perl', 1,
+		'Artistic and GPL'                   => 'perl',         1,
+		'GNU general public license'         => 'gpl',          1,
+		'GNU public license'                 => 'gpl',          1,
+		'GNU lesser general public license'  => 'lgpl',         1,
+		'GNU lesser public license'          => 'lgpl',         1,
+		'GNU library general public license' => 'lgpl',         1,
+		'GNU library public license'         => 'lgpl',         1,
+		'GNU Free Documentation license'     => 'unrestricted', 1,
+		'GNU Affero General Public License'  => 'open_source',  1,
+		'(?:Free)?BSD license'               => 'bsd',          1,
+		'Artistic license 2\.0'              => 'artistic_2',   1,
+		'Artistic license'                   => 'artistic',     1,
+		'Apache (?:Software )?license'       => 'apache',       1,
+		'GPL'                                => 'gpl',          1,
+		'LGPL'                               => 'lgpl',         1,
+		'BSD'                                => 'bsd',          1,
+		'Artistic'                           => 'artistic',     1,
+		'MIT'                                => 'mit',          1,
+		'Mozilla Public License'             => 'mozilla',      1,
+		'Q Public License'                   => 'open_source',  1,
+		'OpenSSL License'                    => 'unrestricted', 1,
+		'SSLeay License'                     => 'unrestricted', 1,
+		'zlib License'                       => 'open_source',  1,
+		'proprietary'                        => 'proprietary',  0,
+	);
+	while ( my ($pattern, $license, $osi) = splice(@phrases, 0, 3) ) {
+		$pattern =~ s#\s+#\\s+#gs;
+		if ( $license_text =~ /\b$pattern\b/i ) {
+			return $license;
+		}
+	}
+	return '';
+}
+
+sub license_from {
+	my $self = shift;
+	if (my $license=_extract_license(Module::Install::_read($_[0]))) {
+		$self->license($license);
+	} else {
+		warn "Cannot determine license info from $_[0]\n";
+		return 'unknown';
+	}
+}
+
+sub _extract_bugtracker {
+	my @links   = $_[0] =~ m#L<(
+	 https?\Q://rt.cpan.org/\E[^>]+|
+	 https?\Q://github.com/\E[\w_]+/[\w_]+/issues|
+	 https?\Q://code.google.com/p/\E[\w_\-]+/issues/list
+	 )>#gx;
+	my %links;
+	@links{@links}=();
+	@links=keys %links;
+	return @links;
+}
+
+sub bugtracker_from {
+	my $self    = shift;
+	my $content = Module::Install::_read($_[0]);
+	my @links   = _extract_bugtracker($content);
+	unless ( @links ) {
+		warn "Cannot determine bugtracker info from $_[0]\n";
+		return 0;
+	}
+	if ( @links > 1 ) {
+		warn "Found more than one bugtracker link in $_[0]\n";
+		return 0;
+	}
+
+	# Set the bugtracker
+	bugtracker( $links[0] );
+	return 1;
+}
+
+sub requires_from {
+	my $self     = shift;
+	my $content  = Module::Install::_readperl($_[0]);
+	my @requires = $content =~ m/^use\s+([^\W\d]\w*(?:::\w+)*)\s+(v?[\d\.]+)/mg;
+	while ( @requires ) {
+		my $module  = shift @requires;
+		my $version = shift @requires;
+		$self->requires( $module => $version );
+	}
+}
+
+sub test_requires_from {
+	my $self     = shift;
+	my $content  = Module::Install::_readperl($_[0]);
+	my @requires = $content =~ m/^use\s+([^\W\d]\w*(?:::\w+)*)\s+([\d\.]+)/mg;
+	while ( @requires ) {
+		my $module  = shift @requires;
+		my $version = shift @requires;
+		$self->test_requires( $module => $version );
+	}
+}
+
+# Convert triple-part versions (eg, 5.6.1 or 5.8.9) to
+# numbers (eg, 5.006001 or 5.008009).
+# Also, convert double-part versions (eg, 5.8)
+sub _perl_version {
+	my $v = $_[-1];
+	$v =~ s/^([1-9])\.([1-9]\d?\d?)$/sprintf("%d.%03d",$1,$2)/e;
+	$v =~ s/^([1-9])\.([1-9]\d?\d?)\.(0|[1-9]\d?\d?)$/sprintf("%d.%03d%03d",$1,$2,$3 || 0)/e;
+	$v =~ s/(\.\d\d\d)000$/$1/;
+	$v =~ s/_.+$//;
+	if ( ref($v) ) {
+		# Numify
+		$v = $v + 0;
+	}
+	return $v;
+}
+
+sub add_metadata {
+    my $self = shift;
+    my %hash = @_;
+    for my $key (keys %hash) {
+        warn "add_metadata: $key is not prefixed with 'x_'.\n" .
+             "Use appopriate function to add non-private metadata.\n" unless $key =~ /^x_/;
+        $self->{values}->{$key} = $hash{$key};
+    }
+}
+
+
+######################################################################
+# MYMETA Support
+
+sub WriteMyMeta {
+	die "WriteMyMeta has been deprecated";
+}
+
+sub write_mymeta_yaml {
+	my $self = shift;
+
+	# We need YAML::Tiny to write the MYMETA.yml file
+	unless ( eval { require YAML::Tiny; 1; } ) {
+		return 1;
+	}
+
+	# Generate the data
+	my $meta = $self->_write_mymeta_data or return 1;
+
+	# Save as the MYMETA.yml file
+	print "Writing MYMETA.yml\n";
+	YAML::Tiny::DumpFile('MYMETA.yml', $meta);
+}
+
+sub write_mymeta_json {
+	my $self = shift;
+
+	# We need JSON to write the MYMETA.json file
+	unless ( eval { require JSON; 1; } ) {
+		return 1;
+	}
+
+	# Generate the data
+	my $meta = $self->_write_mymeta_data or return 1;
+
+	# Save as the MYMETA.yml file
+	print "Writing MYMETA.json\n";
+	Module::Install::_write(
+		'MYMETA.json',
+		JSON->new->pretty(1)->canonical->encode($meta),
+	);
+}
+
+sub _write_mymeta_data {
+	my $self = shift;
+
+	# If there's no existing META.yml there is nothing we can do
+	return undef unless -f 'META.yml';
+
+	# We need Parse::CPAN::Meta to load the file
+	unless ( eval { require Parse::CPAN::Meta; 1; } ) {
+		return undef;
+	}
+
+	# Merge the perl version into the dependencies
+	my $val  = $self->Meta->{values};
+	my $perl = delete $val->{perl_version};
+	if ( $perl ) {
+		$val->{requires} ||= [];
+		my $requires = $val->{requires};
+
+		# Canonize to three-dot version after Perl 5.6
+		if ( $perl >= 5.006 ) {
+			$perl =~ s{^(\d+)\.(\d\d\d)(\d*)}{join('.', $1, int($2||0), int($3||0))}e
+		}
+		unshift @$requires, [ perl => $perl ];
+	}
+
+	# Load the advisory META.yml file
+	my @yaml = Parse::CPAN::Meta::LoadFile('META.yml');
+	my $meta = $yaml[0];
+
+	# Overwrite the non-configure dependency hashes
+	delete $meta->{requires};
+	delete $meta->{build_requires};
+	delete $meta->{recommends};
+	if ( exists $val->{requires} ) {
+		$meta->{requires} = { map { @$_ } @{ $val->{requires} } };
+	}
+	if ( exists $val->{build_requires} ) {
+		$meta->{build_requires} = { map { @$_ } @{ $val->{build_requires} } };
+	}
+
+	return $meta;
+}
+
+1;
diff --git a/inc/Module/Install/RTx.pm b/inc/Module/Install/RTx.pm
new file mode 100644
index 0000000..2889ece
--- /dev/null
+++ b/inc/Module/Install/RTx.pm
@@ -0,0 +1,316 @@
+#line 1
+package Module::Install::RTx;
+
+use 5.008;
+use strict;
+use warnings;
+no warnings 'once';
+
+use Term::ANSIColor qw(:constants);
+use Module::Install::Base;
+use base 'Module::Install::Base';
+our $VERSION = '0.43';
+
+use FindBin;
+use File::Glob     ();
+use File::Basename ();
+
+my @DIRS = qw(etc lib html static bin sbin po var);
+my @INDEX_DIRS = qw(lib bin sbin);
+
+sub RTx {
+    my ( $self, $name, $extra_args ) = @_;
+    $extra_args ||= {};
+
+    # Set up names
+    my $fname = $name;
+    $fname =~ s!-!/!g;
+
+    $self->name( $name )
+        unless $self->name;
+    $self->all_from( "lib/$fname.pm" )
+        unless $self->version;
+    $self->abstract("$name Extension")
+        unless $self->abstract;
+    unless ( $extra_args->{no_readme_generation} ) {
+        $self->readme_from( "lib/$fname.pm",
+                            { options => [ quotes => "none" ] } );
+    }
+    $self->add_metadata("x_module_install_rtx_version", $VERSION );
+
+    my $installdirs = $ENV{INSTALLDIRS};
+    for ( @ARGV ) {
+        if ( /INSTALLDIRS=(.*)/ ) {
+            $installdirs = $1;
+        }
+    }
+
+    # Try to find RT.pm
+    my @prefixes = qw( /opt /usr/local /home /usr /sw /usr/share/request-tracker4);
+    $ENV{RTHOME} =~ s{/RT\.pm$}{} if defined $ENV{RTHOME};
+    $ENV{RTHOME} =~ s{/lib/?$}{}  if defined $ENV{RTHOME};
+    my @try = $ENV{RTHOME} ? ($ENV{RTHOME}, "$ENV{RTHOME}/lib") : ();
+    while (1) {
+        my @look = @INC;
+        unshift @look, grep {defined and -d $_} @try;
+        push @look, grep {defined and -d $_}
+            map { ( "$_/rt5/lib", "$_/lib/rt5", "$_/rt4/lib", "$_/lib/rt4", "$_/lib" ) } @prefixes;
+        last if eval {local @INC = @look; require RT; $RT::LocalLibPath};
+
+        warn
+            "Cannot find the location of RT.pm that defines \$RT::LocalPath in: @look\n";
+        my $given = $self->prompt("Path to directory containing your RT.pm:") or exit;
+        $given =~ s{/RT\.pm$}{};
+        $given =~ s{/lib/?$}{};
+        @try = ($given, "$given/lib");
+    }
+
+    print "Using RT configuration from $INC{'RT.pm'}:\n";
+
+    my $local_lib_path = $RT::LocalLibPath;
+    unshift @INC, $local_lib_path;
+    my $lib_path = File::Basename::dirname( $INC{'RT.pm'} );
+    unshift @INC, $lib_path;
+
+    # Set a baseline minimum version
+    unless ( $extra_args->{deprecated_rt} ) {
+        $self->requires_rt('4.0.0');
+    }
+
+    my $package = $name;
+    $package =~ s/-/::/g;
+    if ( $RT::CORED_PLUGINS{$package} ) {
+        my ($base_version) = $RT::VERSION =~ /(\d+\.\d+\.\d+)/;
+        die RED, <<"EOT";
+
+**** Error: Your installed version of RT ($RT::VERSION) already
+            contains this extension in core, so you don't need to
+            install it.
+
+            Check https://docs.bestpractical.com/rt/$base_version/RT_Config.html
+            to configure it.
+
+EOT
+    }
+
+    # Installation locations
+    my %path;
+    my $plugin_path;
+    if ( $installdirs && $installdirs eq 'vendor' ) {
+        $plugin_path = $RT::PluginPath;
+    } else {
+        $plugin_path = $RT::LocalPluginPath;
+    }
+    $path{$_} = $plugin_path . "/$name/$_"
+        foreach @DIRS;
+
+    # Copy RT 4.2.0 static files into NoAuth; insufficient for
+    # images, but good enough for css and js.
+    $path{static} = "$path{html}/NoAuth/"
+        unless $RT::StaticPath;
+
+    # Delete the ones we don't need
+    delete $path{$_} for grep {not -d "$FindBin::Bin/$_"} keys %path;
+
+    my %index = map { $_ => 1 } @INDEX_DIRS;
+    $self->no_index( directory => $_ ) foreach grep !$index{$_}, @DIRS;
+
+    my $args = join ', ', map "q($_)", map { ($_, "\$(DESTDIR)$path{$_}") }
+        sort keys %path;
+
+    printf "%-10s => %s\n", $_, $path{$_} for sort keys %path;
+
+    if ( my @dirs = map { ( -D => $_ ) } grep $path{$_}, qw(bin html sbin etc) ) {
+        my @po = map { ( -o => $_ ) }
+            grep -f,
+            File::Glob::bsd_glob("po/*.po");
+        $self->postamble(<< ".") if @po;
+lexicons ::
+\t\$(NOECHO) \$(PERL) -MLocale::Maketext::Extract::Run=xgettext -e \"xgettext(qw(@dirs @po))\"
+.
+    }
+
+    my $remove_files;
+    if( $extra_args->{'remove_files'} ){
+        $self->include('Module::Install::RTx::Remove');
+        our @remove_files;
+        eval { require "./etc/upgrade/remove_files" }
+          or print "No remove file located, no files to remove\n";
+        $remove_files = join ",", map {"q(\$(DESTDIR)$plugin_path/$name/$_)"} @remove_files;
+    }
+
+    $self->include('Module::Install::RTx::Runtime') if $self->admin;
+    $self->include_deps( 'YAML::Tiny', 0 ) if $self->admin;
+    my $postamble = << ".";
+install ::
+\t\$(NOECHO) \$(PERL) -Ilib -I"$local_lib_path" -I"$lib_path" -Iinc -MModule::Install::RTx::Runtime -e"RTxPlugin()"
+.
+
+    if( $remove_files ){
+        $postamble .= << ".";
+\t\$(NOECHO) \$(PERL) -MModule::Install::RTx::Remove -e \"RTxRemove([$remove_files])\"
+.
+    }
+
+    $postamble .= << ".";
+\t\$(NOECHO) \$(PERL) -MExtUtils::Install -e \"install({$args})\"
+.
+
+    if ( $path{var} and -d $RT::MasonDataDir ) {
+        my ( $uid, $gid ) = ( stat($RT::MasonDataDir) )[ 4, 5 ];
+        $postamble .= << ".";
+\t\$(NOECHO) chown -R $uid:$gid $path{var}
+.
+    }
+
+    my %has_etc;
+    if ( File::Glob::bsd_glob("$FindBin::Bin/etc/schema.*") ) {
+        $has_etc{schema}++;
+    }
+    if ( File::Glob::bsd_glob("$FindBin::Bin/etc/acl.*") ) {
+        $has_etc{acl}++;
+    }
+    if ( -e 'etc/initialdata' ) { $has_etc{initialdata}++; }
+    if ( grep { /\d+\.\d+\.\d+.*$/ } glob('etc/upgrade/*.*.*') ) {
+        $has_etc{upgrade}++;
+    }
+
+    $self->postamble("$postamble\n");
+    if ( $path{lib} ) {
+        $self->makemaker_args( INSTALLSITELIB => $path{'lib'} );
+        $self->makemaker_args( INSTALLARCHLIB => $path{'lib'} );
+        $self->makemaker_args( INSTALLVENDORLIB => $path{'lib'} )
+    } else {
+        $self->makemaker_args( PM => { "" => "" }, );
+    }
+
+    $self->makemaker_args( INSTALLSITEMAN1DIR => "$RT::LocalPath/man/man1" );
+    $self->makemaker_args( INSTALLSITEMAN3DIR => "$RT::LocalPath/man/man3" );
+    $self->makemaker_args( INSTALLSITEARCH => "$RT::LocalPath/man" );
+
+    # INSTALLDIRS=vendor should install manpages into /usr/share/man.
+    # That is the default path in most distributions. Need input from
+    # Redhat, Centos etc.
+    $self->makemaker_args( INSTALLVENDORMAN1DIR => "/usr/share/man/man1" );
+    $self->makemaker_args( INSTALLVENDORMAN3DIR => "/usr/share/man/man3" );
+    $self->makemaker_args( INSTALLVENDORARCH => "/usr/share/man" );
+
+    if (%has_etc) {
+        print "For first-time installation, type 'make initdb'.\n";
+        my $initdb = '';
+        $initdb .= <<"." if $has_etc{schema};
+\t\$(NOECHO) \$(PERL) -Ilib -I"$local_lib_path" -I"$lib_path" -Iinc -MModule::Install::RTx::Runtime -e"RTxDatabase(qw(schema \$(NAME) \$(VERSION)))"
+.
+        $initdb .= <<"." if $has_etc{acl};
+\t\$(NOECHO) \$(PERL) -Ilib -I"$local_lib_path" -I"$lib_path" -Iinc -MModule::Install::RTx::Runtime -e"RTxDatabase(qw(acl \$(NAME) \$(VERSION)))"
+.
+        $initdb .= <<"." if $has_etc{initialdata};
+\t\$(NOECHO) \$(PERL) -Ilib -I"$local_lib_path" -I"$lib_path" -Iinc -MModule::Install::RTx::Runtime -e"RTxDatabase(qw(insert \$(NAME) \$(VERSION)))"
+.
+        $self->postamble("initdb ::\n$initdb\n");
+        $self->postamble("initialize-database ::\n$initdb\n");
+        if ($has_etc{upgrade}) {
+            print "To upgrade from a previous version of this extension, use 'make upgrade-database'\n";
+            my $upgradedb = qq|\t\$(NOECHO) \$(PERL) -Ilib -I"$local_lib_path" -I"$lib_path" -Iinc -MModule::Install::RTx::Runtime -e"RTxDatabase(qw(upgrade \$(NAME) \$(VERSION)))"\n|;
+            $self->postamble("upgrade-database ::\n$upgradedb\n");
+            $self->postamble("upgradedb ::\n$upgradedb\n");
+        }
+    }
+
+}
+
+sub requires_rt {
+    my ($self,$version) = @_;
+
+    _load_rt_handle();
+
+    if ($self->is_admin) {
+        $self->add_metadata("x_requires_rt", $version);
+        my @sorted = sort RT::Handle::cmp_version $version,'4.0.0';
+        $self->perl_version('5.008003') if $sorted[0] eq '4.0.0'
+            and (not $self->perl_version or '5.008003' > $self->perl_version);
+        @sorted = sort RT::Handle::cmp_version $version,'4.2.0';
+        $self->perl_version('5.010001') if $sorted[0] eq '4.2.0'
+            and (not $self->perl_version or '5.010001' > $self->perl_version);
+    }
+
+    # if we're exactly the same version as what we want, silently return
+    return if ($version eq $RT::VERSION);
+
+    my @sorted = sort RT::Handle::cmp_version $version,$RT::VERSION;
+
+    if ($sorted[-1] eq $version) {
+        die RED, <<"EOT";
+
+**** Error: This extension requires RT $version. Your installed version
+            of RT ($RT::VERSION) is too old.
+
+EOT
+    }
+}
+
+sub requires_rt_plugin {
+    my $self = shift;
+    my ( $plugin ) = @_;
+
+    if ($self->is_admin) {
+        my $plugins = $self->Meta->{values}{"x_requires_rt_plugins"} || [];
+        push @{$plugins}, $plugin;
+        $self->add_metadata("x_requires_rt_plugins", $plugins);
+    }
+
+    my $path = $plugin;
+    $path =~ s{\:\:}{-}g;
+    $path = "$RT::LocalPluginPath/$path/lib";
+    if ( -e $path ) {
+        unshift @INC, $path;
+    } else {
+        my $name = $self->name;
+        my $msg = <<"EOT";
+
+**** Warning: $name requires that the $plugin plugin be installed and
+              enabled; it does not appear to be installed.
+EOT
+        warn RED, $msg, RESET, "\n";
+    }
+    $self->requires(@_);
+}
+
+sub rt_too_new {
+    my ($self,$version,$msg) = @_;
+    my $name = $self->name;
+    $msg ||= <<EOT;
+
+**** Warning: Your installed version of RT (%s) is too new; this extension
+              has not been tested on your version of RT and may not work as expected.
+EOT
+    $self->add_metadata("x_rt_too_new", $version) if $self->is_admin;
+
+    _load_rt_handle();
+    my @sorted = sort RT::Handle::cmp_version $version,$RT::VERSION;
+
+    if ($sorted[0] eq $version) {
+        warn RED, sprintf($msg,$RT::VERSION), RESET, "\n";
+    }
+}
+
+# RT::Handle runs FinalizeDatabaseType which calls RT->Config->Get
+# On 3.8, this dies.  On 4.0/4.2 ->Config transparently runs LoadConfig.
+# LoadConfig requires being able to read RT_SiteConfig.pm (root) so we'd
+# like to avoid pushing that on users.
+# Fake up just enough Config to let FinalizeDatabaseType finish, and
+# anyone later calling LoadConfig will overwrite our shenanigans.
+sub _load_rt_handle {
+    unless ($RT::Config) {
+        require RT::Config;
+        $RT::Config = RT::Config->new;
+        RT->Config->Set('DatabaseType','mysql');
+    }
+    require RT::Handle;
+}
+
+1;
+
+__END__
+
+#line 484
diff --git a/inc/Module/Install/RTx/Runtime.pm b/inc/Module/Install/RTx/Runtime.pm
new file mode 100644
index 0000000..ae07502
--- /dev/null
+++ b/inc/Module/Install/RTx/Runtime.pm
@@ -0,0 +1,80 @@
+#line 1
+package Module::Install::RTx::Runtime;
+
+use base 'Exporter';
+our @EXPORT = qw/RTxDatabase RTxPlugin/;
+
+use strict;
+use File::Basename ();
+
+sub _rt_runtime_load {
+    require RT;
+
+    eval { RT::LoadConfig(); };
+    if (my $err = $@) {
+        die $err unless $err =~ /^RT couldn't load RT config file/m;
+        my $warn = <<EOT;
+This usually means that your current user cannot read the file.  You
+will likely need to run this installation step as root, or some user
+with more permissions.
+EOT
+        $err =~ s/This usually means.*/$warn/s;
+        die $err;
+    }
+}
+
+sub RTxDatabase {
+    my ($action, $name, $version) = @_;
+
+    _rt_runtime_load();
+
+    require RT::System;
+    my $has_upgrade = RT::System->can('AddUpgradeHistory');
+
+    my $lib_path = File::Basename::dirname($INC{'RT.pm'});
+    my @args = (
+        "-I.",
+        "-Ilib",
+        "-I$RT::LocalLibPath",
+        "-I$lib_path",
+        "$RT::SbinPath/rt-setup-database",
+        "--action"      => $action,
+        ($action eq 'upgrade' ? () : ("--datadir"     => "etc")),
+        (($action eq 'insert') ? ("--datafile"    => "etc/initialdata") : ()),
+        "--dba"         => $RT::DatabaseAdmin || $RT::DatabaseUser,
+        "--prompt-for-dba-password" => '',
+        ($has_upgrade ? ("--package" => $name, "--ext-version" => $version) : ()),
+    );
+    # If we're upgrading against an RT which isn't at least 4.2 (has
+    # AddUpgradeHistory) then pass --package.  Upgrades against later RT
+    # releases will pick up --package from AddUpgradeHistory.
+    if ($action eq 'upgrade' and not $has_upgrade) {
+        push @args, "--package" => $name;
+    }
+
+    print "$^X @args\n";
+    (system($^X, @args) == 0) or die "...returned with error: $?\n";
+}
+
+sub RTxPlugin {
+    my ($name) = @_;
+
+    _rt_runtime_load();
+    require YAML::Tiny;
+    my $data = YAML::Tiny::LoadFile('META.yml');
+    my $name = $data->{name};
+
+    my @enabled = RT->Config->Get('Plugins');
+    for my $required (@{$data->{x_requires_rt_plugins} || []}) {
+        next if grep {$required eq $_} @enabled;
+
+        warn <<"EOT";
+
+**** Warning: $name requires that the $required plugin be installed and
+              enabled; it is not currently in \@Plugins.
+
+EOT
+    }
+}
+
+1;
diff --git a/inc/Module/Install/ReadmeFromPod.pm b/inc/Module/Install/ReadmeFromPod.pm
new file mode 100644
index 0000000..3738232
--- /dev/null
+++ b/inc/Module/Install/ReadmeFromPod.pm
@@ -0,0 +1,184 @@
+#line 1
+package Module::Install::ReadmeFromPod;
+
+use 5.006;
+use strict;
+use warnings;
+use base qw(Module::Install::Base);
+use vars qw($VERSION);
+
+$VERSION = '0.30';
+
+{
+
+    # these aren't defined until after _require_admin is run, so
+    # define them so prototypes are available during compilation.
+    sub io;
+    sub capture(&;@);
+
+#line 28
+
+    my $done = 0;
+
+    sub _require_admin {
+
+	# do this once to avoid redefinition warnings from IO::All
+	return if $done;
+
+	require IO::All;
+	IO::All->import( '-binary' );
+
+	require Capture::Tiny;
+	Capture::Tiny->import ( 'capture' );
+
+	return;
+    }
+
+}
+
+sub readme_from {
+  my $self = shift;
+  return unless $self->is_admin;
+
+  _require_admin;
+
+  # Input file
+  my $in_file  = shift || $self->_all_from
+    or die "Can't determine file to make readme_from";
+
+  # Get optional arguments
+  my ($clean, $format, $out_file, $options);
+  my $args = shift;
+  if ( ref $args ) {
+    # Arguments are in a hashref
+    if ( ref($args) ne 'HASH' ) {
+      die "Expected a hashref but got a ".ref($args)."\n";
+    } else {
+      $clean    = $args->{'clean'};
+      $format   = $args->{'format'};
+      $out_file = $args->{'output_file'};
+      $options  = $args->{'options'};
+    }
+  } else {
+    # Arguments are in a list
+    $clean    = $args;
+    $format   = shift;
+    $out_file = shift;
+    $options  = \@_;
+  }
+
+  # Default values;
+  $clean  ||= 0;
+  $format ||= 'txt';
+
+  # Generate README
+  print "readme_from $in_file to $format\n";
+  if ($format =~ m/te?xt/) {
+    $out_file = $self->_readme_txt($in_file, $out_file, $options);
+  } elsif ($format =~ m/html?/) {
+    $out_file = $self->_readme_htm($in_file, $out_file, $options);
+  } elsif ($format eq 'man') {
+    $out_file = $self->_readme_man($in_file, $out_file, $options);
+  } elsif ($format eq 'md') {
+    $out_file = $self->_readme_md($in_file, $out_file, $options);
+  } elsif ($format eq 'pdf') {
+    $out_file = $self->_readme_pdf($in_file, $out_file, $options);
+  }
+
+  if ($clean) {
+    $self->clean_files($out_file);
+  }
+
+  return 1;
+}
+
+
+sub _readme_txt {
+  my ($self, $in_file, $out_file, $options) = @_;
+  $out_file ||= 'README';
+  require Pod::Text;
+  my $parser = Pod::Text->new( @$options );
+  my $io = io->file($out_file)->open(">");
+  my $out_fh = $io->io_handle;
+  $parser->output_fh( *$out_fh );
+  $parser->parse_file( $in_file );
+  return $out_file;
+}
+
+
+sub _readme_htm {
+  my ($self, $in_file, $out_file, $options) = @_;
+  $out_file ||= 'README.htm';
+  require Pod::Html;
+  my ($o) = capture {
+    Pod::Html::pod2html(
+      "--infile=$in_file",
+      "--outfile=-",
+      @$options,
+    );
+  };
+  io->file($out_file)->print($o);
+  # Remove temporary files if needed
+  for my $file ('pod2htmd.tmp', 'pod2htmi.tmp') {
+    if (-e $file) {
+      unlink $file or warn "Warning: Could not remove file '$file'.\n$!\n";
+    }
+  }
+  return $out_file;
+}
+
+
+sub _readme_man {
+  my ($self, $in_file, $out_file, $options) = @_;
+  $out_file ||= 'README.1';
+  require Pod::Man;
+  my $parser = Pod::Man->new( @$options );
+  my $io = io->file($out_file)->open(">");
+  my $out_fh = $io->io_handle;
+  $parser->output_fh( *$out_fh );
+  $parser->parse_file( $in_file );
+  return $out_file;
+}
+
+
+sub _readme_pdf {
+  my ($self, $in_file, $out_file, $options) = @_;
+  $out_file ||= 'README.pdf';
+  eval { require App::pod2pdf; }
+    or die "Could not generate $out_file because pod2pdf could not be found\n";
+  my $parser = App::pod2pdf->new( @$options );
+  $parser->parse_from_file($in_file);
+  my ($o) = capture { $parser->output };
+  io->file($out_file)->print($o);
+  return $out_file;
+}
+
+sub _readme_md {
+  my ($self, $in_file, $out_file, $options) = @_;
+  $out_file ||= 'README.md';
+  require Pod::Markdown;
+  my $parser = Pod::Markdown->new( @$options );
+  my $io = io->file($out_file)->open(">");
+  my $out_fh = $io->io_handle;
+  $parser->output_fh( *$out_fh );
+  $parser->parse_file( $in_file );
+  return $out_file;
+}
+
+
+sub _all_from {
+  my $self = shift;
+  return unless $self->admin->{extensions};
+  my ($metadata) = grep {
+    ref($_) eq 'Module::Install::Metadata';
+  } @{$self->admin->{extensions}};
+  return unless $metadata;
+  return $metadata->{values}{all_from} || '';
+}
+
+'Readme!';
+
+__END__
+
+#line 316
+
diff --git a/inc/Module/Install/Win32.pm b/inc/Module/Install/Win32.pm
new file mode 100644
index 0000000..f7aa615
--- /dev/null
+++ b/inc/Module/Install/Win32.pm
@@ -0,0 +1,64 @@
+#line 1
+package Module::Install::Win32;
+
+use strict;
+use Module::Install::Base ();
+
+use vars qw{$VERSION @ISA $ISCORE};
+BEGIN {
+	$VERSION = '1.19';
+	@ISA     = 'Module::Install::Base';
+	$ISCORE  = 1;
+}
+
+# determine if the user needs nmake, and download it if needed
+sub check_nmake {
+	my $self = shift;
+	$self->load('can_run');
+	$self->load('get_file');
+
+	require Config;
+	return unless (
+		$^O eq 'MSWin32'                     and
+		$Config::Config{make}                and
+		$Config::Config{make} =~ /^nmake\b/i and
+		! $self->can_run('nmake')
+	);
+
+	print "The required 'nmake' executable not found, fetching it...\n";
+
+	require File::Basename;
+	my $rv = $self->get_file(
+		url       => 'http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe',
+		ftp_url   => 'ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe',
+		local_dir => File::Basename::dirname($^X),
+		size      => 51928,
+		run       => 'Nmake15.exe /o > nul',
+		check_for => 'Nmake.exe',
+		remove    => 1,
+	);
+
+	die <<'END_MESSAGE' unless $rv;
+
+-------------------------------------------------------------------------------
+
+Since you are using Microsoft Windows, you will need the 'nmake' utility
+before installation. It's available at:
+
+  http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe
+      or
+  ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe
+
+Please download the file manually, save it to a directory in %PATH% (e.g.
+C:\WINDOWS\COMMAND\), then launch the MS-DOS command line shell, "cd" to
+that directory, and run "Nmake15.exe" from there; that will create the
+'nmake.exe' file needed by this module.
+
+You may then resume the installation process described in README.
+
+-------------------------------------------------------------------------------
+END_MESSAGE
+
+}
+
+1;
diff --git a/inc/Module/Install/WriteAll.pm b/inc/Module/Install/WriteAll.pm
new file mode 100644
index 0000000..2db861a
--- /dev/null
+++ b/inc/Module/Install/WriteAll.pm
@@ -0,0 +1,63 @@
+#line 1
+package Module::Install::WriteAll;
+
+use strict;
+use Module::Install::Base ();
+
+use vars qw{$VERSION @ISA $ISCORE};
+BEGIN {
+	$VERSION = '1.19';
+	@ISA     = qw{Module::Install::Base};
+	$ISCORE  = 1;
+}
+
+sub WriteAll {
+	my $self = shift;
+	my %args = (
+		meta        => 1,
+		sign        => 0,
+		inline      => 0,
+		check_nmake => 1,
+		@_,
+	);
+
+	$self->sign(1)                if $args{sign};
+	$self->admin->WriteAll(%args) if $self->is_admin;
+
+	$self->check_nmake if $args{check_nmake};
+	unless ( $self->makemaker_args->{PL_FILES} ) {
+		# XXX: This still may be a bit over-defensive...
+		unless ($self->makemaker(6.25)) {
+			$self->makemaker_args( PL_FILES => {} ) if -f 'Build.PL';
+		}
+	}
+
+	# Until ExtUtils::MakeMaker support MYMETA.yml, make sure
+	# we clean it up properly ourself.
+	$self->realclean_files('MYMETA.yml');
+
+	if ( $args{inline} ) {
+		$self->Inline->write;
+	} else {
+		$self->Makefile->write;
+	}
+
+	# The Makefile write process adds a couple of dependencies,
+	# so write the META.yml files after the Makefile.
+	if ( $args{meta} ) {
+		$self->Meta->write;
+	}
+
+	# Experimental support for MYMETA
+	if ( $ENV{X_MYMETA} ) {
+		if ( $ENV{X_MYMETA} eq 'JSON' ) {
+			$self->Meta->write_mymeta_json;
+		} else {
+			$self->Meta->write_mymeta_yaml;
+		}
+	}
+
+	return 1;
+}
+
+1;
diff --git a/inc/YAML/Tiny.pm b/inc/YAML/Tiny.pm
new file mode 100644
index 0000000..fb157a6
--- /dev/null
+++ b/inc/YAML/Tiny.pm
@@ -0,0 +1,872 @@
+#line 1
+use 5.008001; # sane UTF-8 support
+use strict;
+use warnings;
+package YAML::Tiny; # git description: v1.72-7-g8682f63
+# XXX-INGY is 5.8.1 too old/broken for utf8?
+# XXX-XDG Lancaster consensus was that it was sufficient until
+# proven otherwise
+
+our $VERSION = '1.73';
+
+#####################################################################
+# The YAML::Tiny API.
+#
+# These are the currently documented API functions/methods and
+# exports:
+
+use Exporter;
+our @ISA       = qw{ Exporter  };
+our @EXPORT    = qw{ Load Dump };
+our @EXPORT_OK = qw{ LoadFile DumpFile freeze thaw };
+
+###
+# Functional/Export API:
+
+sub Dump {
+    return YAML::Tiny->new(@_)->_dump_string;
+}
+
+# XXX-INGY Returning last document seems a bad behavior.
+# XXX-XDG I think first would seem more natural, but I don't know
+# that it's worth changing now
+sub Load {
+    my $self = YAML::Tiny->_load_string(@_);
+    if ( wantarray ) {
+        return @$self;
+    } else {
+        # To match YAML.pm, return the last document
+        return $self->[-1];
+    }
+}
+
+# XXX-INGY Do we really need freeze and thaw?
+# XXX-XDG I don't think so.  I'd support deprecating them.
+BEGIN {
+    *freeze = \&Dump;
+    *thaw   = \&Load;
+}
+
+sub DumpFile {
+    my $file = shift;
+    return YAML::Tiny->new(@_)->_dump_file($file);
+}
+
+sub LoadFile {
+    my $file = shift;
+    my $self = YAML::Tiny->_load_file($file);
+    if ( wantarray ) {
+        return @$self;
+    } else {
+        # Return only the last document to match YAML.pm,
+        return $self->[-1];
+    }
+}
+
+
+###
+# Object Oriented API:
+
+# Create an empty YAML::Tiny object
+# XXX-INGY Why do we use ARRAY object?
+# NOTE: I get it now, but I think it's confusing and not needed.
+# Will change it on a branch later, for review.
+#
+# XXX-XDG I don't support changing it yet.  It's a very well-documented
+# "API" of YAML::Tiny.  I'd support deprecating it, but Adam suggested
+# we not change it until YAML.pm's own OO API is established so that
+# users only have one API change to digest, not two
+sub new {
+    my $class = shift;
+    bless [ @_ ], $class;
+}
+
+# XXX-INGY It probably doesn't matter, and it's probably too late to
+# change, but 'read/write' are the wrong names. Read and Write
+# are actions that take data from storage to memory
+# characters/strings. These take the data to/from storage to native
+# Perl objects, which the terms dump and load are meant. As long as
+# this is a legacy quirk to YAML::Tiny it's ok, but I'd prefer not
+# to add new {read,write}_* methods to this API.
+
+sub read_string {
+    my $self = shift;
+    $self->_load_string(@_);
+}
+
+sub write_string {
+    my $self = shift;
+    $self->_dump_string(@_);
+}
+
+sub read {
+    my $self = shift;
+    $self->_load_file(@_);
+}
+
+sub write {
+    my $self = shift;
+    $self->_dump_file(@_);
+}
+
+
+
+
+#####################################################################
+# Constants
+
+# Printed form of the unprintable characters in the lowest range
+# of ASCII characters, listed by ASCII ordinal position.
+my @UNPRINTABLE = qw(
+    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
+);
+
+# Printable characters for escapes
+my %UNESCAPES = (
+    0 => "\x00", z => "\x00", N    => "\x85",
+    a => "\x07", b => "\x08", t    => "\x09",
+    n => "\x0a", v => "\x0b", f    => "\x0c",
+    r => "\x0d", e => "\x1b", '\\' => '\\',
+);
+
+# XXX-INGY
+# I(ngy) need to decide if these values should be quoted in
+# YAML::Tiny or not. Probably yes.
+
+# These 3 values have special meaning when unquoted and using the
+# default YAML schema. They need quotes if they are strings.
+my %QUOTE = map { $_ => 1 } qw{
+    null true false
+};
+
+# The commented out form is simpler, but overloaded the Perl regex
+# engine due to recursion and backtracking problems on strings
+# larger than 32,000ish characters. Keep it for reference purposes.
+# qr/\"((?:\\.|[^\"])*)\"/
+my $re_capture_double_quoted = qr/\"([^\\"]*(?:\\.[^\\"]*)*)\"/;
+my $re_capture_single_quoted = qr/\'([^\']*(?:\'\'[^\']*)*)\'/;
+# unquoted re gets trailing space that needs to be stripped
+my $re_capture_unquoted_key  = qr/([^:]+(?::+\S(?:[^:]*|.*?(?=:)))*)(?=\s*\:(?:\s+|$))/;
+my $re_trailing_comment      = qr/(?:\s+\#.*)?/;
+my $re_key_value_separator   = qr/\s*:(?:\s+(?:\#.*)?|$)/;
+
+
+
+
+
+#####################################################################
+# YAML::Tiny Implementation.
+#
+# These are the private methods that do all the work. They may change
+# at any time.
+
+
+###
+# Loader functions:
+
+# Create an object from a file
+sub _load_file {
+    my $class = ref $_[0] ? ref shift : shift;
+
+    # Check the file
+    my $file = shift or $class->_error( 'You did not specify a file name' );
+    $class->_error( "File '$file' does not exist" )
+        unless -e $file;
+    $class->_error( "'$file' is a directory, not a file" )
+        unless -f _;
+    $class->_error( "Insufficient permissions to read '$file'" )
+        unless -r _;
+
+    # Open unbuffered with strict UTF-8 decoding and no translation layers
+    open( my $fh, "<:unix:encoding(UTF-8)", $file );
+    unless ( $fh ) {
+        $class->_error("Failed to open file '$file': $!");
+    }
+
+    # flock if available (or warn if not possible for OS-specific reasons)
+    if ( _can_flock() ) {
+        flock( $fh, Fcntl::LOCK_SH() )
+            or warn "Couldn't lock '$file' for reading: $!";
+    }
+
+    # slurp the contents
+    my $contents = eval {
+        use warnings FATAL => 'utf8';
+        local $/;
+        <$fh>
+    };
+    if ( my $err = $@ ) {
+        $class->_error("Error reading from file '$file': $err");
+    }
+
+    # close the file (release the lock)
+    unless ( close $fh ) {
+        $class->_error("Failed to close file '$file': $!");
+    }
+
+    $class->_load_string( $contents );
+}
+
+# Create an object from a string
+sub _load_string {
+    my $class  = ref $_[0] ? ref shift : shift;
+    my $self   = bless [], $class;
+    my $string = $_[0];
+    eval {
+        unless ( defined $string ) {
+            die \"Did not provide a string to load";
+        }
+
+        # Check if Perl has it marked as characters, but it's internally
+        # inconsistent.  E.g. maybe latin1 got read on a :utf8 layer
+        if ( utf8::is_utf8($string) && ! utf8::valid($string) ) {
+            die \<<'...';
+Read an invalid UTF-8 string (maybe mixed UTF-8 and 8-bit character set).
+Did you decode with lax ":utf8" instead of strict ":encoding(UTF-8)"?
+...
+        }
+
+        # Ensure Unicode character semantics, even for 0x80-0xff
+        utf8::upgrade($string);
+
+        # Check for and strip any leading UTF-8 BOM
+        $string =~ s/^\x{FEFF}//;
+
+        # Check for some special cases
+        return $self unless length $string;
+
+        # Split the file into lines
+        my @lines = grep { ! /^\s*(?:\#.*)?\z/ }
+                split /(?:\015{1,2}\012|\015|\012)/, $string;
+
+        # Strip the initial YAML header
+        @lines and $lines[0] =~ /^\%YAML[: ][\d\.]+.*\z/ and shift @lines;
+
+        # A nibbling parser
+        my $in_document = 0;
+        while ( @lines ) {
+            # Do we have a document header?
+            if ( $lines[0] =~ /^---\s*(?:(.+)\s*)?\z/ ) {
+                # Handle scalar documents
+                shift @lines;
+                if ( defined $1 and $1 !~ /^(?:\#.+|\%YAML[: ][\d\.]+)\z/ ) {
+                    push @$self,
+                        $self->_load_scalar( "$1", [ undef ], \@lines );
+                    next;
+                }
+                $in_document = 1;
+            }
+
+            if ( ! @lines or $lines[0] =~ /^(?:---|\.\.\.)/ ) {
+                # A naked document
+                push @$self, undef;
+                while ( @lines and $lines[0] !~ /^---/ ) {
+                    shift @lines;
+                }
+                $in_document = 0;
+
+            # XXX The final '-+$' is to look for -- which ends up being an
+            # error later.
+            } elsif ( ! $in_document && @$self ) {
+                # only the first document can be explicit
+                die \"YAML::Tiny failed to classify the line '$lines[0]'";
+            } elsif ( $lines[0] =~ /^\s*\-(?:\s|$|-+$)/ ) {
+                # An array at the root
+                my $document = [ ];
+                push @$self, $document;
+                $self->_load_array( $document, [ 0 ], \@lines );
+
+            } elsif ( $lines[0] =~ /^(\s*)\S/ ) {
+                # A hash at the root
+                my $document = { };
+                push @$self, $document;
+                $self->_load_hash( $document, [ length($1) ], \@lines );
+
+            } else {
+                # Shouldn't get here.  @lines have whitespace-only lines
+                # stripped, and previous match is a line with any
+                # non-whitespace.  So this clause should only be reachable via
+                # a perlbug where \s is not symmetric with \S
+
+                # uncoverable statement
+                die \"YAML::Tiny failed to classify the line '$lines[0]'";
+            }
+        }
+    };
+    my $err = $@;
+    if ( ref $err eq 'SCALAR' ) {
+        $self->_error(${$err});
+    } elsif ( $err ) {
+        $self->_error($err);
+    }
+
+    return $self;
+}
+
+sub _unquote_single {
+    my ($self, $string) = @_;
+    return '' unless length $string;
+    $string =~ s/\'\'/\'/g;
+    return $string;
+}
+
+sub _unquote_double {
+    my ($self, $string) = @_;
+    return '' unless length $string;
+    $string =~ s/\\"/"/g;
+    $string =~
+        s{\\([Nnever\\fartz0b]|x([0-9a-fA-F]{2}))}
+         {(length($1)>1)?pack("H2",$2):$UNESCAPES{$1}}gex;
+    return $string;
+}
+
+# Load a YAML scalar string to the actual Perl scalar
+sub _load_scalar {
+    my ($self, $string, $indent, $lines) = @_;
+
+    # Trim trailing whitespace
+    $string =~ s/\s*\z//;
+
+    # Explitic null/undef
+    return undef if $string eq '~';
+
+    # Single quote
+    if ( $string =~ /^$re_capture_single_quoted$re_trailing_comment\z/ ) {
+        return $self->_unquote_single($1);
+    }
+
+    # Double quote.
+    if ( $string =~ /^$re_capture_double_quoted$re_trailing_comment\z/ ) {
+        return $self->_unquote_double($1);
+    }
+
+    # Special cases
+    if ( $string =~ /^[\'\"!&]/ ) {
+        die \"YAML::Tiny does not support a feature in line '$string'";
+    }
+    return {} if $string =~ /^{}(?:\s+\#.*)?\z/;
+    return [] if $string =~ /^\[\](?:\s+\#.*)?\z/;
+
+    # Regular unquoted string
+    if ( $string !~ /^[>|]/ ) {
+        die \"YAML::Tiny found illegal characters in plain scalar: '$string'"
+            if $string =~ /^(?:-(?:\s|$)|[\@\%\`])/ or
+                $string =~ /:(?:\s|$)/;
+        $string =~ s/\s+#.*\z//;
+        return $string;
+    }
+
+    # Error
+    die \"YAML::Tiny failed to find multi-line scalar content" unless @$lines;
+
+    # Check the indent depth
+    $lines->[0]   =~ /^(\s*)/;
+    $indent->[-1] = length("$1");
+    if ( defined $indent->[-2] and $indent->[-1] <= $indent->[-2] ) {
+        die \"YAML::Tiny found bad indenting in line '$lines->[0]'";
+    }
+
+    # Pull the lines
+    my @multiline = ();
+    while ( @$lines ) {
+        $lines->[0] =~ /^(\s*)/;
+        last unless length($1) >= $indent->[-1];
+        push @multiline, substr(shift(@$lines), $indent->[-1]);
+    }
+
+    my $j = (substr($string, 0, 1) eq '>') ? ' ' : "\n";
+    my $t = (substr($string, 1, 1) eq '-') ? ''  : "\n";
+    return join( $j, @multiline ) . $t;
+}
+
+# Load an array
+sub _load_array {
+    my ($self, $array, $indent, $lines) = @_;
+
+    while ( @$lines ) {
+        # Check for a new document
+        if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
+            while ( @$lines and $lines->[0] !~ /^---/ ) {
+                shift @$lines;
+            }
+            return 1;
+        }
+
+        # Check the indent level
+        $lines->[0] =~ /^(\s*)/;
+        if ( length($1) < $indent->[-1] ) {
+            return 1;
+        } elsif ( length($1) > $indent->[-1] ) {
+            die \"YAML::Tiny found bad indenting in line '$lines->[0]'";
+        }
+
+        if ( $lines->[0] =~ /^(\s*\-\s+)[^\'\"]\S*\s*:(?:\s+|$)/ ) {
+            # Inline nested hash
+            my $indent2 = length("$1");
+            $lines->[0] =~ s/-/ /;
+            push @$array, { };
+            $self->_load_hash( $array->[-1], [ @$indent, $indent2 ], $lines );
+
+        } elsif ( $lines->[0] =~ /^\s*\-\s*\z/ ) {
+            shift @$lines;
+            unless ( @$lines ) {
+                push @$array, undef;
+                return 1;
+            }
+            if ( $lines->[0] =~ /^(\s*)\-/ ) {
+                my $indent2 = length("$1");
+                if ( $indent->[-1] == $indent2 ) {
+                    # Null array entry
+                    push @$array, undef;
+                } else {
+                    # Naked indenter
+                    push @$array, [ ];
+                    $self->_load_array(
+                        $array->[-1], [ @$indent, $indent2 ], $lines
+                    );
+                }
+
+            } elsif ( $lines->[0] =~ /^(\s*)\S/ ) {
+                push @$array, { };
+                $self->_load_hash(
+                    $array->[-1], [ @$indent, length("$1") ], $lines
+                );
+
+            } else {
+                die \"YAML::Tiny failed to classify line '$lines->[0]'";
+            }
+
+        } elsif ( $lines->[0] =~ /^\s*\-(\s*)(.+?)\s*\z/ ) {
+            # Array entry with a value
+            shift @$lines;
+            push @$array, $self->_load_scalar(
+                "$2", [ @$indent, undef ], $lines
+            );
+
+        } elsif ( defined $indent->[-2] and $indent->[-1] == $indent->[-2] ) {
+            # This is probably a structure like the following...
+            # ---
+            # foo:
+            # - list
+            # bar: value
+            #
+            # ... so lets return and let the hash parser handle it
+            return 1;
+
+        } else {
+            die \"YAML::Tiny failed to classify line '$lines->[0]'";
+        }
+    }
+
+    return 1;
+}
+
+# Load a hash
+sub _load_hash {
+    my ($self, $hash, $indent, $lines) = @_;
+
+    while ( @$lines ) {
+        # Check for a new document
+        if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
+            while ( @$lines and $lines->[0] !~ /^---/ ) {
+                shift @$lines;
+            }
+            return 1;
+        }
+
+        # Check the indent level
+        $lines->[0] =~ /^(\s*)/;
+        if ( length($1) < $indent->[-1] ) {
+            return 1;
+        } elsif ( length($1) > $indent->[-1] ) {
+            die \"YAML::Tiny found bad indenting in line '$lines->[0]'";
+        }
+
+        # Find the key
+        my $key;
+
+        # Quoted keys
+        if ( $lines->[0] =~
+            s/^\s*$re_capture_single_quoted$re_key_value_separator//
+        ) {
+            $key = $self->_unquote_single($1);
+        }
+        elsif ( $lines->[0] =~
+            s/^\s*$re_capture_double_quoted$re_key_value_separator//
+        ) {
+            $key = $self->_unquote_double($1);
+        }
+        elsif ( $lines->[0] =~
+            s/^\s*$re_capture_unquoted_key$re_key_value_separator//
+        ) {
+            $key = $1;
+            $key =~ s/\s+$//;
+        }
+        elsif ( $lines->[0] =~ /^\s*\?/ ) {
+            die \"YAML::Tiny does not support a feature in line '$lines->[0]'";
+        }
+        else {
+            die \"YAML::Tiny failed to classify line '$lines->[0]'";
+        }
+
+        if ( exists $hash->{$key} ) {
+            warn "YAML::Tiny found a duplicate key '$key' in line '$lines->[0]'";
+        }
+
+        # Do we have a value?
+        if ( length $lines->[0] ) {
+            # Yes
+            $hash->{$key} = $self->_load_scalar(
+                shift(@$lines), [ @$indent, undef ], $lines
+            );
+        } else {
+            # An indent
+            shift @$lines;
+            unless ( @$lines ) {
+                $hash->{$key} = undef;
+                return 1;
+            }
+            if ( $lines->[0] =~ /^(\s*)-/ ) {
+                $hash->{$key} = [];
+                $self->_load_array(
+                    $hash->{$key}, [ @$indent, length($1) ], $lines
+                );
+            } elsif ( $lines->[0] =~ /^(\s*)./ ) {
+                my $indent2 = length("$1");
+                if ( $indent->[-1] >= $indent2 ) {
+                    # Null hash entry
+                    $hash->{$key} = undef;
+                } else {
+                    $hash->{$key} = {};
+                    $self->_load_hash(
+                        $hash->{$key}, [ @$indent, length($1) ], $lines
+                    );
+                }
+            }
+        }
+    }
+
+    return 1;
+}
+
+
+###
+# Dumper functions:
+
+# Save an object to a file
+sub _dump_file {
+    my $self = shift;
+
+    require Fcntl;
+
+    # Check the file
+    my $file = shift or $self->_error( 'You did not specify a file name' );
+
+    my $fh;
+    # flock if available (or warn if not possible for OS-specific reasons)
+    if ( _can_flock() ) {
+        # Open without truncation (truncate comes after lock)
+        my $flags = Fcntl::O_WRONLY()|Fcntl::O_CREAT();
+        sysopen( $fh, $file, $flags )
+            or $self->_error("Failed to open file '$file' for writing: $!");
+
+        # Use no translation and strict UTF-8
+        binmode( $fh, ":raw:encoding(UTF-8)");
+
+        flock( $fh, Fcntl::LOCK_EX() )
+            or warn "Couldn't lock '$file' for reading: $!";
+
+        # truncate and spew contents
+        truncate $fh, 0;
+        seek $fh, 0, 0;
+    }
+    else {
+        open $fh, ">:unix:encoding(UTF-8)", $file;
+    }
+
+    # serialize and spew to the handle
+    print {$fh} $self->_dump_string;
+
+    # close the file (release the lock)
+    unless ( close $fh ) {
+        $self->_error("Failed to close file '$file': $!");
+    }
+
+    return 1;
+}
+
+# Save an object to a string
+sub _dump_string {
+    my $self = shift;
+    return '' unless ref $self && @$self;
+
+    # Iterate over the documents
+    my $indent = 0;
+    my @lines  = ();
+
+    eval {
+        foreach my $cursor ( @$self ) {
+            push @lines, '---';
+
+            # An empty document
+            if ( ! defined $cursor ) {
+                # Do nothing
+
+            # A scalar document
+            } elsif ( ! ref $cursor ) {
+                $lines[-1] .= ' ' . $self->_dump_scalar( $cursor );
+
+            # A list at the root
+            } elsif ( ref $cursor eq 'ARRAY' ) {
+                unless ( @$cursor ) {
+                    $lines[-1] .= ' []';
+                    next;
+                }
+                push @lines, $self->_dump_array( $cursor, $indent, {} );
+
+            # A hash at the root
+            } elsif ( ref $cursor eq 'HASH' ) {
+                unless ( %$cursor ) {
+                    $lines[-1] .= ' {}';
+                    next;
+                }
+                push @lines, $self->_dump_hash( $cursor, $indent, {} );
+
+            } else {
+                die \("Cannot serialize " . ref($cursor));
+            }
+        }
+    };
+    if ( ref $@ eq 'SCALAR' ) {
+        $self->_error(${$@});
+    } elsif ( $@ ) {
+        $self->_error($@);
+    }
+
+    join '', map { "$_\n" } @lines;
+}
+
+sub _has_internal_string_value {
+    my $value = shift;
+    my $b_obj = B::svref_2object(\$value);  # for round trip problem
+    return $b_obj->FLAGS & B::SVf_POK();
+}
+
+sub _dump_scalar {
+    my $string = $_[1];
+    my $is_key = $_[2];
+    # Check this before checking length or it winds up looking like a string!
+    my $has_string_flag = _has_internal_string_value($string);
+    return '~'  unless defined $string;
+    return "''" unless length  $string;
+    if (Scalar::Util::looks_like_number($string)) {
+        # keys and values that have been used as strings get quoted
+        if ( $is_key || $has_string_flag ) {
+            return qq['$string'];
+        }
+        else {
+            return $string;
+        }
+    }
+    if ( $string =~ /[\x00-\x09\x0b-\x0d\x0e-\x1f\x7f-\x9f\'\n]/ ) {
+        $string =~ s/\\/\\\\/g;
+        $string =~ s/"/\\"/g;
+        $string =~ s/\n/\\n/g;
+        $string =~ s/[\x85]/\\N/g;
+        $string =~ s/([\x00-\x1f])/\\$UNPRINTABLE[ord($1)]/g;
+        $string =~ s/([\x7f-\x9f])/'\x' . sprintf("%X",ord($1))/ge;
+        return qq|"$string"|;
+    }
+    if ( $string =~ /(?:^[~!@#%&*|>?:,'"`{}\[\]]|^-+$|\s|:\z)/ or
+        $QUOTE{$string}
+    ) {
+        return "'$string'";
+    }
+    return $string;
+}
+
+sub _dump_array {
+    my ($self, $array, $indent, $seen) = @_;
+    if ( $seen->{refaddr($array)}++ ) {
+        die \"YAML::Tiny does not support circular references";
+    }
+    my @lines  = ();
+    foreach my $el ( @$array ) {
+        my $line = ('  ' x $indent) . '-';
+        my $type = ref $el;
+        if ( ! $type ) {
+            $line .= ' ' . $self->_dump_scalar( $el );
+            push @lines, $line;
+
+        } elsif ( $type eq 'ARRAY' ) {
+            if ( @$el ) {
+                push @lines, $line;
+                push @lines, $self->_dump_array( $el, $indent + 1, $seen );
+            } else {
+                $line .= ' []';
+                push @lines, $line;
+            }
+
+        } elsif ( $type eq 'HASH' ) {
+            if ( keys %$el ) {
+                push @lines, $line;
+                push @lines, $self->_dump_hash( $el, $indent + 1, $seen );
+            } else {
+                $line .= ' {}';
+                push @lines, $line;
+            }
+
+        } else {
+            die \"YAML::Tiny does not support $type references";
+        }
+    }
+
+    @lines;
+}
+
+sub _dump_hash {
+    my ($self, $hash, $indent, $seen) = @_;
+    if ( $seen->{refaddr($hash)}++ ) {
+        die \"YAML::Tiny does not support circular references";
+    }
+    my @lines  = ();
+    foreach my $name ( sort keys %$hash ) {
+        my $el   = $hash->{$name};
+        my $line = ('  ' x $indent) . $self->_dump_scalar($name, 1) . ":";
+        my $type = ref $el;
+        if ( ! $type ) {
+            $line .= ' ' . $self->_dump_scalar( $el );
+            push @lines, $line;
+
+        } elsif ( $type eq 'ARRAY' ) {
+            if ( @$el ) {
+                push @lines, $line;
+                push @lines, $self->_dump_array( $el, $indent + 1, $seen );
+            } else {
+                $line .= ' []';
+                push @lines, $line;
+            }
+
+        } elsif ( $type eq 'HASH' ) {
+            if ( keys %$el ) {
+                push @lines, $line;
+                push @lines, $self->_dump_hash( $el, $indent + 1, $seen );
+            } else {
+                $line .= ' {}';
+                push @lines, $line;
+            }
+
+        } else {
+            die \"YAML::Tiny does not support $type references";
+        }
+    }
+
+    @lines;
+}
+
+
+
+#####################################################################
+# DEPRECATED API methods:
+
+# Error storage (DEPRECATED as of 1.57)
+our $errstr    = '';
+
+# Set error
+sub _error {
+    require Carp;
+    $errstr = $_[1];
+    $errstr =~ s/ at \S+ line \d+.*//;
+    Carp::croak( $errstr );
+}
+
+# Retrieve error
+my $errstr_warned;
+sub errstr {
+    require Carp;
+    Carp::carp( "YAML::Tiny->errstr and \$YAML::Tiny::errstr is deprecated" )
+        unless $errstr_warned++;
+    $errstr;
+}
+
+
+
+
+#####################################################################
+# Helper functions. Possibly not needed.
+
+
+# Use to detect nv or iv
+use B;
+
+# XXX-INGY Is flock YAML::Tiny's responsibility?
+# Some platforms can't flock :-(
+# XXX-XDG I think it is.  When reading and writing files, we ought
+# to be locking whenever possible.  People (foolishly) use YAML
+# files for things like session storage, which has race issues.
+my $HAS_FLOCK;
+sub _can_flock {
+    if ( defined $HAS_FLOCK ) {
+        return $HAS_FLOCK;
+    }
+    else {
+        require Config;
+        my $c = \%Config::Config;
+        $HAS_FLOCK = grep { $c->{$_} } qw/d_flock d_fcntl_can_lock d_lockf/;
+        require Fcntl if $HAS_FLOCK;
+        return $HAS_FLOCK;
+    }
+}
+
+
+# XXX-INGY Is this core in 5.8.1? Can we remove this?
+# XXX-XDG Scalar::Util 1.18 didn't land until 5.8.8, so we need this
+#####################################################################
+# Use Scalar::Util if possible, otherwise emulate it
+
+use Scalar::Util ();
+BEGIN {
+    local $@;
+    if ( eval { Scalar::Util->VERSION(1.18); } ) {
+        *refaddr = *Scalar::Util::refaddr;
+    }
+    else {
+        eval <<'END_PERL';
+# Scalar::Util failed to load or too old
+sub refaddr {
+    my $pkg = ref($_[0]) or return undef;
+    if ( !! UNIVERSAL::can($_[0], 'can') ) {
+        bless $_[0], 'Scalar::Util::Fake';
+    } else {
+        $pkg = undef;
+    }
+    "$_[0]" =~ /0x(\w+)/;
+    my $i = do { no warnings 'portable'; hex $1 };
+    bless $_[0], $pkg if defined $pkg;
+    $i;
+}
+END_PERL
+    }
+}
+
+delete $YAML::Tiny::{refaddr};
+
+1;
+
+# XXX-INGY Doc notes I'm putting up here. Changing the doc when it's wrong
+# but leaving grey area stuff up here.
+#
+# I would like to change Read/Write to Load/Dump below without
+# changing the actual API names.
+#
+# It might be better to put Load/Dump API in the SYNOPSIS instead of the
+# dubious OO API.
+#
+# null and bool explanations may be outdated.
+
+__END__
+
+#line 1487
-----------------------------------------------------------------------


hooks/post-receive
-- 
rt-extension-turbo


More information about the Bps-public-commit mailing list