<commit_before>/* See license.txt for terms of usage */

define([
    "firebug/lib/xpcom",
    "firebug/lib/object",
    "firebug/lib/locale",
    "firebug/lib/domplate",
    "firebug/lib/dom",
    "firebug/lib/options",
    "firebug/lib/persist",
    "firebug/lib/string",
    "firebug/lib/http",
    "firebug/lib/css",
    "firebug/lib/events",
    "firebug/cookies/baseObserver",
    "firebug/chrome/tabWatcher",
    "firebug/cookies/cookieReps",
    "firebug/cookies/cookieUtils",
    "firebug/cookies/cookie",
    "firebug/cookies/breakpoints",
    "firebug/cookies/cookieEvents",
    "firebug/lib/array",
],
function(Xpcom, Obj, Locale, Domplate, Dom, Options, Persist, Str, Http, Css, Events,
    BaseObserver, TabWatcher, CookieReps, CookieUtils, Cookie, Breakpoints, CookieEvents, Arr) {

// ********************************************************************************************* //
// Constants

const Cc = Components.classes;
const Ci = Components.interfaces;

const filterByPath = "cookies.filterByPath";

const panelName = "cookies";

// ********************************************************************************************* //
// Cookie observer

/**
 * @class This class represents an observer (nsIObserver) for cookie-changed
 * and cookie-rejected events. These events are dispatche by Firefox
 * see https://developer.mozilla.org/En/Observer_Notifications.
 */
var CookieObserver = Obj.extend(BaseObserver,
/** @lends CookieObserver */
{
    // nsIObserver
    observe: function(aSubject, aTopic, aData)
    {
        try
        {
            if (!Firebug.CookieModule.isAlwaysEnabled())
                return;

            // See: https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsICookieService
            // For all possible values.
            if (aTopic == "cookie-changed")
            {
                var cookies = []
                if (aData == "batch-deleted")
                {
                    // In this case the subject is nsIArray.
                    var enumerator = aSubject.QueryInterface(Ci.nsIArray).enumerate();
                    while (enumerator.hasMoreElements())
                        cookies.push(enumerator.getNext().QueryInterface(Ci.nsICookie2));

                    // The event will be further distributed as standard "delete" event.
                    aData = "deleted";
                }
                else
                {
                    aSubject = aSubject ? aSubject.QueryInterface(Ci.nsICookie2) : null;
                    cookies.push(aSubject);
                }

                for (var i=0; i<cookies.length; i++)
                    this.iterateContexts(this.onCookieChanged, cookies[i], aData);
            }
            else if (aTopic == "cookie-rejected")
            {
                aSubject = aSubject.QueryInterface(Ci.nsIURI);
                this.iterateContexts(this.onCookieRejected, aSubject, aData);
            }
        }
        catch (err)
        {
            if (FBTrace.DBG_ERRORS)
            {
                FBTrace.sysout("cookies.CookieObserver.observe; ERROR " +
                    aTopic + ", " + aData, err);
                FBTrace.sysout("cookies.CookieObserver.observe; subject ", aSubject);
            }
        }
    },

    iterateContexts: function(fn)
    {
        var oThis = this;
        var args = Arr.cloneArray(arguments);
        TabWatcher.iterateContexts(function(context)
        {
            args[0] = context;
            fn.apply(oThis, args);
        });
    },

    /**
     * @param {String} activeUri This object represents currently active host. Notice that there
     *      can be more active hosts (activeHosts map) on one page in case
     *      of embedded iframes or/and previous redirects.
     *      Properties:
     *      host: www.example.com
     *      path: /subdir/
     *
     * @param {String} host: Represents the host of a cookie for which
     *      we are checking if it should be displayed for the active URI.
     *
     * @param {String} path: Represents the path of a cookie for which
     *      we are checking if it should be displayed for the active URI.
     *
     * @returns {Boolean} If the method returns true the host/path belongs
     *      to the activeUri.
     */
    isHostFromURI: function(activeUri, host, cookiePath)
    {
        var pathFilter = Options.get(filterByPath);

        // Get directory path (without the file name)
        var queryStringPos = activeUri.path.lastIndexOf("?");
        var activePath = queryStringPos != -1 ?
            activeUri.path.substr(0, queryStringPos) : activeUri.path;

        // Remove slash at the end of the active path according to step 4 of RFC 6265 section 5.1.4
        var lastChar = activePath.charAt(activePath.length - 1);
        if (lastChar == "/")
            activePath = activePath.substr(0, activePath.length - 1);

        // If the path filter is on, only cookies that match given path
        // according to RFC 6265 section 5.1.4 will be displayed.
        if (pathFilter && (activePath != cookiePath && !(Str.hasPrefix(activePath, cookiePath) &&
            (cookiePath.charAt(cookiePath.length - 1) == "/" ||
                activePath.substr(cookiePath.length, 1) == "/"))))
        {
            return false;
        }

        // The cookie must belong to given URI from this context,
        // otherwise it won't be displayed in this tab.
        var uri = CookieUtils.makeStrippedHost(activeUri.host);
        if (uri == host)
            return true;

        if (uri.length < host.length)
            return false;

        var h = "." + host;
        var u = "." + uri;
        if (u.substr(u.length - h.length) == h)
            return true;

        return false;
    },

    isHostFromContext: function(context, host, path)
    {
        var location;

        // Invalid in Chromebug.
        try
        {
            location = context.window.location;
            if (!location || !location.protocol)
                return;
        }
        catch (err)
        {
            return false;
        }

        if (location.protocol.indexOf("http") != 0)
            return false;

        var rawHost = CookieUtils.makeStrippedHost(host);

        // Test the current main URI first.
        // The location isn't nsIURI, so make a fake object (aka nsIURI).
        var fakeUri = {host: location.host, path: location.pathname};
        if (this.isHostFromURI(fakeUri, rawHost, path))
            return true;

        // xxxHonza
        // If the context.cookies is not initialized, it's bad. It means that
        // neither temporary context no real context has been initialized
        // One reason is that Sript model issues panel.show in onModuleActivate
        // which consequently requests a file (double load prblem), which
        // consequently rises this cookie event.
        if (!context.cookies)
            return false;

        // Now test if the cookie doesn't belong to some of the
        // activeHosts (redirects, frames).
        var activeHosts = context.cookies.activeHosts;
        for (var activeHost in activeHosts)
        {
            if (this.isHostFromURI(activeHosts[activeHost], rawHost, path))
                return true;
        }

        return false;
    },

    isCookieFromContext: function(context, cookie)
    {
        return this.isHostFromContext(context, cookie.host, cookie.path);
    },

    onCookieChanged: function(context, cookie, action)
    {
        // If the action == "cleared" the cookie is *not* set. This action is triggered
        // when all cookies are removed (cookieManager.removeAll)
        // In such a case let's displaye the event in all contexts.
        if (cookie && !this.isCookieFromContext(context, cookie))
            return;

        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.onCookieChanged: '" + (cookie ? cookie.name : "null") +
                "', " + action);

        if (action != "cleared")
        {
            // If log into the Console tab is on, create "deleted", "added" and "changed" events.
            if (logEvents())
                this.logEvent(new CookieEvents.CookieChangedEvent(context, CookieUtils.makeCookieObject(cookie),
                    action), context, "cookie");

            // Break on cookie if "Break On" is activated or if a cookie breakpoint exist.
            Breakpoints.breakOnCookie(context, cookie, action);
        }

        switch(action)
        {
          case "deleted":
            this.onRemoveCookie(context, cookie);
            break;
          case "added":
            this.onAddCookie(context, cookie);
            break;
          case "changed":
            this.onUpdateCookie(context, cookie);
            break;
          case "cleared":
            this.onClear(context);
            return;
          case "reload":
            context.invalidatePanels(panelName);
            return;
        }
    },

    onClear: function(context)
    {
        var panel = context.getPanel(panelName);
        panel.clear();

        if (logEvents())
            this.logEvent(new CookieEvents.CookieClearedEvent(), context, "cookiesCleared");
    },

    onCookieRejected: function(context, uri)
    {
        var path = uri.path.substr(0, (uri.path.lastIndexOf("/") || 1));
        if (!this.isHostFromContext(context, uri.host, path))
            return;

        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.onCookieRejected: " + uri.spec);

        // Mark host and all its cookies as rejected.
        // xxxHonza there was an exception "context.cookies is undefined".
        var activeHost = context.cookies.activeHosts[uri.host];
        if (activeHost)
            activeHost.rejected = true;

        var receivedCookies = activeHost ? activeHost.receivedCookies : null;
        for (var i=0; receivedCookies && i<receivedCookies.length; i++)
            receivedCookies[i].cookie.rejected = true;

        // Refresh the panel asynchronously.
        context.invalidatePanels(panelName);

        // Bail out if events are not logged into the Console.
        if (!logEvents())
            return;

        // The "cookies-rejected" event is sent even if no cookies
        // from the blocked site have been actually received.
        // So, the receivedCookies array can be null.
        // Don't display anything in the console in that case,
        // there could be a lot of "Cookie Rejected" events.
        // There would be actually one for each embedded request.
        if (!receivedCookies)
            return;

        // Create group log for list of rejected cookies.
        var groupRow = Firebug.Console.openGroup(
            [new CookieEvents.CookieRejectedEvent(context, uri)],
            context, "cookiesRejected", null, true, null, true);

        // The console can be disabled (since FB 1.2).
        if (!groupRow)
            return;

        // It's closed by default.
        Css.removeClass(groupRow, "opened");
        Firebug.Console.closeGroup(context, true);

        // Create embedded table.
        CookieReps.CookieTable.render(receivedCookies, groupRow.lastChild);
    },

    onAddCookie: function(context, cookie)
    {
        var panel = context.getPanel(panelName, true);
        var repCookie = panel ? panel.findRepObject(cookie) : null;
        if (repCookie)
        {
            this.onUpdateCookie(context, cookie);
            return;
        }

        if (!panel || !panel.table)
            return;

        var repCookie = panel ? panel.findRepObject(cookie) : null;

        cookie = new Cookie(CookieUtils.makeCookieObject(cookie));

        var tbody = panel.table.lastChild;
        var parent = tbody.lastChild ? tbody.lastChild : tbody;
        var row = CookieReps.CookieRow.cookieTag.insertRows({cookies: [cookie]}, parent)[0];

        cookie.row = row;
        row.repObject = cookie;

        //xxxHonza the new cookie should respect current sorting.
    },

    onUpdateCookie: function(context, cookie)
    {
        var panel = context.getPanel(panelName, true);

        // The table doesn't have to be initialized yet.
        if (!panel || !panel.table)
            return;

        var repCookie = panel ? panel.findRepObject(cookie) : null;
        if (!repCookie)
        {
            this.onAddCookie(context, cookie);
            return;
        }

        repCookie.cookie = CookieUtils.makeCookieObject(cookie);
        repCookie.rawHost = CookieUtils.makeStrippedHost(cookie.host);

        // These are helpers so, the XML and JSON cookies don't have to be parsed
        // again and again. But we need to reset them if the value is changed.
        repCookie.json = null;
        repCookie.xml = null;

        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.onUpdateCookie: " + cookie.name, repCookie);

        var row = repCookie.row;
        var rowTemplate = CookieReps.CookieRow;

        if (Css.hasClass(row, "opened"))
        {
            var cookieInfoBody = Dom.getElementByClass(row.nextSibling, "cookieInfoBody");

            // Invalidate content of all tabs.
            cookieInfoBody.valuePresented = false;
            cookieInfoBody.rawValuePresented = false;
            cookieInfoBody.xmlPresented = false;
            cookieInfoBody.jsonPresented = false;

            // Update tabs visibility and content of the selected tab.
            rowTemplate.updateTabs(cookieInfoBody, repCookie, context);
            rowTemplate.updateInfo(cookieInfoBody, repCookie, context);
        }

        rowTemplate.updateRow(repCookie, context);
    },

    onRemoveCookie: function(context, cookie)
    {
        var panel = context.getPanel(panelName, true);
        var repCookie = panel ? panel.findRepObject(cookie) : null;
        if (!repCookie)
            return;

        // Remove cookie from UI.
        var row = repCookie.row;
        var parent = repCookie.row.parentNode;

        if (Css.hasClass(repCookie.row, "opened"))
            parent.removeChild(row.nextSibling);

        if (!parent)
            return;

        parent.removeChild(repCookie.row);
    },

    logEvent: function(eventObject, context, className)
    {
        // xxxHonza: if the cookie is changed befor initContext, the log in
        // console is lost.
        Firebug.Console.log(eventObject, context, className, null, true);
    }
});

// ********************************************************************************************* //
// Helpers

function logEvents()
{
    return Options.get("cookies.logEvents");
}

// ********************************************************************************************* //

return CookieObserver;

// ********************************************************************************************* //
});

<commit_msg>Issue 5554 (Cookies from IDN domains are not shown) http://code.google.com/p/fbug/issues/detail?id=5554<commit_after>/* See license.txt for terms of usage */

define([
    "firebug/lib/xpcom",
    "firebug/lib/object",
    "firebug/lib/locale",
    "firebug/lib/domplate",
    "firebug/lib/dom",
    "firebug/lib/options",
    "firebug/lib/persist",
    "firebug/lib/string",
    "firebug/lib/http",
    "firebug/lib/css",
    "firebug/lib/events",
    "firebug/cookies/baseObserver",
    "firebug/chrome/tabWatcher",
    "firebug/cookies/cookieReps",
    "firebug/cookies/cookieUtils",
    "firebug/cookies/cookie",
    "firebug/cookies/breakpoints",
    "firebug/cookies/cookieEvents",
    "firebug/lib/array",
],
function(Xpcom, Obj, Locale, Domplate, Dom, Options, Persist, Str, Http, Css, Events,
    BaseObserver, TabWatcher, CookieReps, CookieUtils, Cookie, Breakpoints, CookieEvents, Arr) {

// ********************************************************************************************* //
// Constants

const Cc = Components.classes;
const Ci = Components.interfaces;

const filterByPath = "cookies.filterByPath";

const panelName = "cookies";

const idnService = Xpcom.CCSV("@mozilla.org/network/idn-service;1", "nsIIDNService");

// ********************************************************************************************* //
// Cookie observer

/**
 * @class This class represents an observer (nsIObserver) for cookie-changed
 * and cookie-rejected events. These events are dispatche by Firefox
 * see https://developer.mozilla.org/En/Observer_Notifications.
 */
var CookieObserver = Obj.extend(BaseObserver,
/** @lends CookieObserver */
{
    // nsIObserver
    observe: function(aSubject, aTopic, aData)
    {
        try
        {
            if (!Firebug.CookieModule.isAlwaysEnabled())
                return;

            // See: https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsICookieService
            // For all possible values.
            if (aTopic == "cookie-changed")
            {
                var cookies = []
                if (aData == "batch-deleted")
                {
                    // In this case the subject is nsIArray.
                    var enumerator = aSubject.QueryInterface(Ci.nsIArray).enumerate();
                    while (enumerator.hasMoreElements())
                        cookies.push(enumerator.getNext().QueryInterface(Ci.nsICookie2));

                    // The event will be further distributed as standard "delete" event.
                    aData = "deleted";
                }
                else
                {
                    aSubject = aSubject ? aSubject.QueryInterface(Ci.nsICookie2) : null;
                    cookies.push(aSubject);
                }

                for (var i=0; i<cookies.length; i++)
                    this.iterateContexts(this.onCookieChanged, cookies[i], aData);
            }
            else if (aTopic == "cookie-rejected")
            {
                aSubject = aSubject.QueryInterface(Ci.nsIURI);
                this.iterateContexts(this.onCookieRejected, aSubject, aData);
            }
        }
        catch (err)
        {
            if (FBTrace.DBG_ERRORS)
            {
                FBTrace.sysout("cookies.CookieObserver.observe; ERROR " +
                    aTopic + ", " + aData, err);
                FBTrace.sysout("cookies.CookieObserver.observe; subject ", aSubject);
            }
        }
    },

    iterateContexts: function(fn)
    {
        var oThis = this;
        var args = Arr.cloneArray(arguments);
        TabWatcher.iterateContexts(function(context)
        {
            args[0] = context;
            fn.apply(oThis, args);
        });
    },

    /**
     * @param {String} activeUri This object represents currently active host. Notice that there
     *      can be more active hosts (activeHosts map) on one page in case
     *      of embedded iframes or/and previous redirects.
     *      Properties:
     *      host: www.example.com
     *      path: /subdir/
     *
     * @param {String} host: Represents the host of a cookie for which
     *      we are checking if it should be displayed for the active URI.
     *
     * @param {String} path: Represents the path of a cookie for which
     *      we are checking if it should be displayed for the active URI.
     *
     * @returns {Boolean} If the method returns true the host/path belongs
     *      to the activeUri.
     */
    isHostFromURI: function(activeUri, host, cookiePath)
    {
        var pathFilter = Options.get(filterByPath);

        // Get directory path (without the file name)
        var queryStringPos = activeUri.path.lastIndexOf("?");
        var activePath = queryStringPos != -1 ?
            activeUri.path.substr(0, queryStringPos) : activeUri.path;

        // Remove slash at the end of the active path according to step 4 of RFC 6265 section 5.1.4
        var lastChar = activePath.charAt(activePath.length - 1);
        if (lastChar == "/")
            activePath = activePath.substr(0, activePath.length - 1);

        // If the path filter is on, only cookies that match given path
        // according to RFC 6265 section 5.1.4 will be displayed.
        if (pathFilter && (activePath != cookiePath && !(Str.hasPrefix(activePath, cookiePath) &&
            (cookiePath.charAt(cookiePath.length - 1) == "/" ||
                activePath.substr(cookiePath.length, 1) == "/"))))
        {
            return false;
        }

        // The cookie must belong to given URI from this context,
        // otherwise it won't be displayed in this tab.
        var uri = CookieUtils.makeStrippedHost(activeUri.host);
        if (uri == host)
            return true;

        if (uri.length < host.length)
            return false;

        var h = "." + host;
        var u = "." + uri;
        if (u.substr(u.length - h.length) == h)
            return true;

        return false;
    },

    isHostFromContext: function(context, host, path)
    {
        var location;
        try
        {
            host = idnService.convertACEtoUTF8(host);
        }
        catch(exc)
        {
            if (FBTrace.DBG_ERRORS || FBTrace.DBG_COOKIES)
                FBTrace.sysout("Host could not be converted to UTF-8", exc);
        }

        // Invalid in Chromebug.
        try
        {
            location = context.window.location;
            if (!location || !location.protocol)
                return;
        }
        catch (err)
        {
            return false;
        }

        if (location.protocol.indexOf("http") != 0)
            return false;

        var rawHost = CookieUtils.makeStrippedHost(host);

        // Test the current main URI first.
        // The location isn't nsIURI, so make a fake object (aka nsIURI).
        var fakeUri = {host: location.host, path: location.pathname};
        if (this.isHostFromURI(fakeUri, rawHost, path))
            return true;

        // xxxHonza
        // If the context.cookies is not initialized, it's bad. It means that
        // neither temporary context no real context has been initialized
        // One reason is that Sript model issues panel.show in onModuleActivate
        // which consequently requests a file (double load prblem), which
        // consequently rises this cookie event.
        if (!context.cookies)
            return false;

        // Now test if the cookie doesn't belong to some of the
        // activeHosts (redirects, frames).
        var activeHosts = context.cookies.activeHosts;
        for (var activeHost in activeHosts)
        {
            if (this.isHostFromURI(activeHosts[activeHost], rawHost, path))
                return true;
        }

        return false;
    },

    isCookieFromContext: function(context, cookie)
    {
        return this.isHostFromContext(context, cookie.host, cookie.path);
    },

    onCookieChanged: function(context, cookie, action)
    {
        // If the action == "cleared" the cookie is *not* set. This action is triggered
        // when all cookies are removed (cookieManager.removeAll)
        // In such a case let's displaye the event in all contexts.
        if (cookie && !this.isCookieFromContext(context, cookie))
            return;

        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.onCookieChanged: '" + (cookie ? cookie.name : "null") +
                "', " + action);

        if (action != "cleared")
        {
            // If log into the Console tab is on, create "deleted", "added" and "changed" events.
            if (logEvents())
                this.logEvent(new CookieEvents.CookieChangedEvent(context, CookieUtils.makeCookieObject(cookie),
                    action), context, "cookie");

            // Break on cookie if "Break On" is activated or if a cookie breakpoint exist.
            Breakpoints.breakOnCookie(context, cookie, action);
        }

        switch(action)
        {
          case "deleted":
            this.onRemoveCookie(context, cookie);
            break;
          case "added":
            this.onAddCookie(context, cookie);
            break;
          case "changed":
            this.onUpdateCookie(context, cookie);
            break;
          case "cleared":
            this.onClear(context);
            return;
          case "reload":
            context.invalidatePanels(panelName);
            return;
        }
    },

    onClear: function(context)
    {
        var panel = context.getPanel(panelName);
        panel.clear();

        if (logEvents())
            this.logEvent(new CookieEvents.CookieClearedEvent(), context, "cookiesCleared");
    },

    onCookieRejected: function(context, uri)
    {
        var path = uri.path.substr(0, (uri.path.lastIndexOf("/") || 1));
        if (!this.isHostFromContext(context, uri.host, path))
            return;

        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.onCookieRejected: " + uri.spec);

        // Mark host and all its cookies as rejected.
        // xxxHonza there was an exception "context.cookies is undefined".
        var activeHost = context.cookies.activeHosts[uri.host];
        if (activeHost)
            activeHost.rejected = true;

        var receivedCookies = activeHost ? activeHost.receivedCookies : null;
        for (var i=0; receivedCookies && i<receivedCookies.length; i++)
            receivedCookies[i].cookie.rejected = true;

        // Refresh the panel asynchronously.
        context.invalidatePanels(panelName);

        // Bail out if events are not logged into the Console.
        if (!logEvents())
            return;

        // The "cookies-rejected" event is sent even if no cookies
        // from the blocked site have been actually received.
        // So, the receivedCookies array can be null.
        // Don't display anything in the console in that case,
        // there could be a lot of "Cookie Rejected" events.
        // There would be actually one for each embedded request.
        if (!receivedCookies)
            return;

        // Create group log for list of rejected cookies.
        var groupRow = Firebug.Console.openGroup(
            [new CookieEvents.CookieRejectedEvent(context, uri)],
            context, "cookiesRejected", null, true, null, true);

        // The console can be disabled (since FB 1.2).
        if (!groupRow)
            return;

        // It's closed by default.
        Css.removeClass(groupRow, "opened");
        Firebug.Console.closeGroup(context, true);

        // Create embedded table.
        CookieReps.CookieTable.render(receivedCookies, groupRow.lastChild);
    },

    onAddCookie: function(context, cookie)
    {
        var panel = context.getPanel(panelName, true);
        var repCookie = panel ? panel.findRepObject(cookie) : null;
        if (repCookie)
        {
            this.onUpdateCookie(context, cookie);
            return;
        }

        if (!panel || !panel.table)
            return;

        var repCookie = panel ? panel.findRepObject(cookie) : null;

        cookie = new Cookie(CookieUtils.makeCookieObject(cookie));

        var tbody = panel.table.lastChild;
        var parent = tbody.lastChild ? tbody.lastChild : tbody;
        var row = CookieReps.CookieRow.cookieTag.insertRows({cookies: [cookie]}, parent)[0];

        cookie.row = row;
        row.repObject = cookie;

        //xxxHonza the new cookie should respect current sorting.
    },

    onUpdateCookie: function(context, cookie)
    {
        var panel = context.getPanel(panelName, true);

        // The table doesn't have to be initialized yet.
        if (!panel || !panel.table)
            return;

        var repCookie = panel ? panel.findRepObject(cookie) : null;
        if (!repCookie)
        {
            this.onAddCookie(context, cookie);
            return;
        }

        repCookie.cookie = CookieUtils.makeCookieObject(cookie);
        repCookie.rawHost = CookieUtils.makeStrippedHost(cookie.host);

        // These are helpers so, the XML and JSON cookies don't have to be parsed
        // again and again. But we need to reset them if the value is changed.
        repCookie.json = null;
        repCookie.xml = null;

        if (FBTrace.DBG_COOKIES)
            FBTrace.sysout("cookies.onUpdateCookie: " + cookie.name, repCookie);

        var row = repCookie.row;
        var rowTemplate = CookieReps.CookieRow;

        if (Css.hasClass(row, "opened"))
        {
            var cookieInfoBody = Dom.getElementByClass(row.nextSibling, "cookieInfoBody");

            // Invalidate content of all tabs.
            cookieInfoBody.valuePresented = false;
            cookieInfoBody.rawValuePresented = false;
            cookieInfoBody.xmlPresented = false;
            cookieInfoBody.jsonPresented = false;

            // Update tabs visibility and content of the selected tab.
            rowTemplate.updateTabs(cookieInfoBody, repCookie, context);
            rowTemplate.updateInfo(cookieInfoBody, repCookie, context);
        }

        rowTemplate.updateRow(repCookie, context);
    },

    onRemoveCookie: function(context, cookie)
    {
        var panel = context.getPanel(panelName, true);
        var repCookie = panel ? panel.findRepObject(cookie) : null;
        if (!repCookie)
            return;

        // Remove cookie from UI.
        var row = repCookie.row;
        var parent = repCookie.row.parentNode;

        if (Css.hasClass(repCookie.row, "opened"))
            parent.removeChild(row.nextSibling);

        if (!parent)
            return;

        parent.removeChild(repCookie.row);
    },

    logEvent: function(eventObject, context, className)
    {
        // xxxHonza: if the cookie is changed befor initContext, the log in
        // console is lost.
        Firebug.Console.log(eventObject, context, className, null, true);
    }
});

// ********************************************************************************************* //
// Helpers

function logEvents()
{
    return Options.get("cookies.logEvents");
}

// ********************************************************************************************* //

return CookieObserver;

// ********************************************************************************************* //
});

<|endoftext|>


<commit_before>$(document).ready(function(){
    var nofinv = 1;
    $('#id_invoicenumber').addClass('invoice'+' '+nofinv);
    $('#id_invoicestatus').addClass('invoiceadditional'+' '+nofinv);
    $('.addhiddendata:first').find('#id_invoicenumber').attr('id', 'id_invoicenumber'+nofinv);
    $('.addhiddendata:first').find('#id_invoicestatus').attr('id', 'id_invoicestatus'+nofinv);

    $('#trbutton').on('click', function(){
	$('#trbutton').fadeOut('slow', function(){
	    $('#trform').fadeIn('slow');
	    $('#ptrack').html('Please fill in all the fields.');
	});
    });

    $('#id_reminderdate').datepicker();
    $('.paid:first').datepicker();

    var $name = $('#vclerk').attr('value');
    $('#id_clerk').val($name);

    $('#id_clerk').attr('readonly', true);
    $('#id_actiondate').attr('readonly', true);

    $('#id_market').on('change', function(){
	$('#id_ccode option').each(function(){
	    $(this).show();
	});

	var market = $('#id_market').val();
	if (market == 'IT') {
	    var marketArray = ['', '11', '13', '15', '16'];
	} else if (market == 'DE') {
	    var marketArray = ['', '13', '52', '55', '60', '64', '66'];
	} else if (market == 'PT' || market == 'PL' || market == 'NL' || market == 'NO') {
	    var marketArray = ['', '10'];
	} else if (market == 'BE') {
	    var marketArray = ['', '12', '14', '18', '31'];
	} else if (market == 'SE') {
	    var marketArray = ['', '16'];
	} else if (market == 'FI') {
	    var marketArray = ['', '11'];
	} else if (market == 'DK') {
	    var marketArray = ['', '12'];
	} else {
	    return false;
	}

	$('#id_ccode option').each(function(){
	    var ccodeOpt = $(this).val();
	    if ($.inArray(ccodeOpt, marketArray) > -1){
		;
	    } else {
		$(this).hide();
	    }
	});

    });

    $(document).on('change', '.status', function() {
	var status = $(this).val();
	var nextform = $(this).parent().parent().parent().next();

	if (nextform.is(':hidden')){
	    ;
	} else {
	    nextform.hide();
	    if (nextform.find('.paid').is(':hidden')) {nextform.find('.paid').show()};
	    if (nextform.find('.reject').is(':hidden')) {nextform.find('.reject').show()};
	    nextform.find('label').show();
	}

	if (status == 'RJ'){
	    nextform.find('.paid').hide();
	    nextform.find('label:last').hide();
	    nextform.fadeIn('slow');
	} else if (status == 'PD') {
	    nextform.find('.reject').hide();
	    nextform.find('label:first').hide();
	    nextform.fadeIn('slow');
	};
    });

    $('#addbutt').on('click', function() {
	if (nofinv == 1) {
	    $('.addhiddendata:first').find('#id_invoicenumber').attr('id', 'id_invoicenumber'+nofinv);
	    $('.addhiddendata:first').find('#id_invoicestatus').attr('id', 'id_invoicestatus'+nofinv);
	}

	var formToApp = $('.addhiddendata:first').clone();
	formToApp.find('button').remove();
	formToApp.find('.paid').removeClass('hasDatepicker');
	formToApp.find('#id_invoicenumber1').removeClass('invoice'+' '+nofinv);
	formToApp.find('#id_invoicestatus1').removeClass('invoiceadditional'+' '+nofinv);
	nofinv = nofinv + 1;
	formToApp.find('.paid').val('');
	formToApp.find('.paid').attr('name', 'paid'+nofinv);
	formToApp.find('.reject').attr('name', 'reject'+nofinv);
	formToApp.find('.paid').attr('id', 'id_paidon'+nofinv);
	formToApp.find('.reject').attr('id', 'id_rejectreason'+nofinv);
	formToApp.find('#id_invoicenumber1').addClass('invoice'+' '+nofinv);
	formToApp.find('#id_invoicenumber1').attr('name', 'invoicenumber'+nofinv);
	formToApp.find('#id_invoicenumber1').attr('id', 'id_invoicenumber'+nofinv);
	formToApp.find('#id_invoicestatus1').addClass('invoiceadditional'+' '+nofinv);
	formToApp.find('#id_invoicestatus1').attr('name', 'invoicestatus'+nofinv);
	formToApp.find('#id_invoicestatus1').attr('id', 'id_invoicestatus'+nofinv);
	$('#vendorform').before(formToApp);
	formToApp.find('.paid').datepicker();
    });

    $('#rembutt').on('click', function() {
	// need to modify if the item is the last, actually is a bug because it's removed as well
	$('.addhiddendata:last').remove();
    });

    $('#trackbutton').on('mouseover', function() {
	// get the total number of invoices.
	var invo = $('.invoice').length;

	var data = []

	//loop through the invoice to create our data to post
	for (x = 1; x <= invo; x++) {
	    var formn = 'form'+x;
	    var obj = {
		    market: $('#id_market').val(),
		    ccode: $('#id_ccode').val(),
		    level: $('').val(),
		    clerk: $('#id_clerk').val(),
		    actiondate: $('#id_actiondate').val(),
		    reminderdate: $('#id_actiondate').val(),
		    remindernumber: $('#id_remindernumber').val(),
		    vendor: $('#id_vendor').val(),
		    mailvendor: $('#id_mailvendor').val(),
		    invoicenumber: $('#id_invoicenumber'+x).val(),
		    invoicestatus: $('#id_invoicestatus'+x).val(),
		    actiontaken: $('#id_actiontaken').val(),
		    rejectreason: $('#id_rejectreason'+x).val(),
		    paidon: $('#id_paidon'+x).val()
	    };
	    data.push(obj);
	}
	alert(JSON.stringify(data));

	//setup the AJAX request
	$.ajaxSetup({
	    type: 'POST',
	    dataType: 'json'
	});

	//the real AJAX request
	$.ajax({
	    url: '/ajax/',
	    data: {
		form_type: 'multi',
		mass_data: JSON.stringify(data)
	    },
	    success: alert('Posted to /ajax/'),
	    error: function (ajaxObj, textStatus, error) {
		alert(error);
	    }
	});
	return true;
    });
});
<commit_msg>Removed because there is no more difference if we will post one ore more invoices.<commit_after>$(document).ready(function(){
    var nofinv = 1;
    $('#id_invoicenumber').addClass('invoice'+' '+nofinv);
    $('#id_invoicestatus').addClass('invoiceadditional'+' '+nofinv);
    $('.addhiddendata:first').find('#id_invoicenumber').attr('id', 'id_invoicenumber'+nofinv);
    $('.addhiddendata:first').find('#id_invoicestatus').attr('id', 'id_invoicestatus'+nofinv);

    $('#trbutton').on('click', function(){
	$('#trbutton').fadeOut('slow', function(){
	    $('#trform').fadeIn('slow');
	    $('#ptrack').html('Please fill in all the fields.');
	});
    });

    $('#id_reminderdate').datepicker();
    $('.paid:first').datepicker();

    var $name = $('#vclerk').attr('value');
    $('#id_clerk').val($name);

    $('#id_clerk').attr('readonly', true);
    $('#id_actiondate').attr('readonly', true);

    $('#id_market').on('change', function(){
	$('#id_ccode option').each(function(){
	    $(this).show();
	});

	var market = $('#id_market').val();
	if (market == 'IT') {
	    var marketArray = ['', '11', '13', '15', '16'];
	} else if (market == 'DE') {
	    var marketArray = ['', '13', '52', '55', '60', '64', '66'];
	} else if (market == 'PT' || market == 'PL' || market == 'NL' || market == 'NO') {
	    var marketArray = ['', '10'];
	} else if (market == 'BE') {
	    var marketArray = ['', '12', '14', '18', '31'];
	} else if (market == 'SE') {
	    var marketArray = ['', '16'];
	} else if (market == 'FI') {
	    var marketArray = ['', '11'];
	} else if (market == 'DK') {
	    var marketArray = ['', '12'];
	} else {
	    return false;
	}

	$('#id_ccode option').each(function(){
	    var ccodeOpt = $(this).val();
	    if ($.inArray(ccodeOpt, marketArray) > -1){
		;
	    } else {
		$(this).hide();
	    }
	});

    });

    $(document).on('change', '.status', function() {
	var status = $(this).val();
	var nextform = $(this).parent().parent().parent().next();

	if (nextform.is(':hidden')){
	    ;
	} else {
	    nextform.hide();
	    if (nextform.find('.paid').is(':hidden')) {nextform.find('.paid').show()};
	    if (nextform.find('.reject').is(':hidden')) {nextform.find('.reject').show()};
	    nextform.find('label').show();
	}

	if (status == 'RJ'){
	    nextform.find('.paid').hide();
	    nextform.find('label:last').hide();
	    nextform.fadeIn('slow');
	} else if (status == 'PD') {
	    nextform.find('.reject').hide();
	    nextform.find('label:first').hide();
	    nextform.fadeIn('slow');
	};
    });

    $('#addbutt').on('click', function() {
	var formToApp = $('.addhiddendata:first').clone();
	formToApp.find('button').remove();
	formToApp.find('.paid').removeClass('hasDatepicker');
	formToApp.find('#id_invoicenumber1').removeClass('invoice'+' '+nofinv);
	formToApp.find('#id_invoicestatus1').removeClass('invoiceadditional'+' '+nofinv);
	nofinv = nofinv + 1;
	formToApp.find('.paid').val('');
	formToApp.find('.paid').attr('name', 'paid'+nofinv);
	formToApp.find('.reject').attr('name', 'reject'+nofinv);
	formToApp.find('.paid').attr('id', 'id_paidon'+nofinv);
	formToApp.find('.reject').attr('id', 'id_rejectreason'+nofinv);
	formToApp.find('#id_invoicenumber1').addClass('invoice'+' '+nofinv);
	formToApp.find('#id_invoicenumber1').attr('name', 'invoicenumber'+nofinv);
	formToApp.find('#id_invoicenumber1').attr('id', 'id_invoicenumber'+nofinv);
	formToApp.find('#id_invoicestatus1').addClass('invoiceadditional'+' '+nofinv);
	formToApp.find('#id_invoicestatus1').attr('name', 'invoicestatus'+nofinv);
	formToApp.find('#id_invoicestatus1').attr('id', 'id_invoicestatus'+nofinv);
	$('#vendorform').before(formToApp);
	formToApp.find('.paid').datepicker();
    });

    $('#rembutt').on('click', function() {
	// need to modify if the item is the last, actually is a bug because it's removed as well
	$('.addhiddendata:last').remove();
    });

    $('#trackbutton').on('mouseover', function() {
	// get the total number of invoices.
	var invo = $('.invoice').length;

	var data = []

	//loop through the invoice to create our data to post
	for (x = 1; x <= invo; x++) {
	    var formn = 'form'+x;
	    var obj = {
		    market: $('#id_market').val(),
		    ccode: $('#id_ccode').val(),
		    level: $('').val(),
		    clerk: $('#id_clerk').val(),
		    actiondate: $('#id_actiondate').val(),
		    reminderdate: $('#id_actiondate').val(),
		    remindernumber: $('#id_remindernumber').val(),
		    vendor: $('#id_vendor').val(),
		    mailvendor: $('#id_mailvendor').val(),
		    invoicenumber: $('#id_invoicenumber'+x).val(),
		    invoicestatus: $('#id_invoicestatus'+x).val(),
		    actiontaken: $('#id_actiontaken').val(),
		    rejectreason: $('#id_rejectreason'+x).val(),
		    paidon: $('#id_paidon'+x).val()
	    };
	    data.push(obj);
	}
	alert(JSON.stringify(data));

	//setup the AJAX request
	$.ajaxSetup({
	    type: 'POST',
	    dataType: 'json'
	});

	//the real AJAX request
	$.ajax({
	    url: '/ajax/',
	    data: {
		form_type: 'multi',
		mass_data: JSON.stringify(data)
	    },
	    success: alert('Posted to /ajax/'),
	    error: function (ajaxObj, textStatus, error) {
		alert(error);
	    }
	});
	return true;
    });
});
<|endoftext|>


<commit_before>// @flow

import invariant from 'invariant';

import { sortIDs } from 'lib/shared/relationship-utils';
import {
  type RelationshipRequest,
  type RelationshipErrors,
  type UndirectedRelationshipRow,
  relationshipActions,
  undirectedStatus,
  directedStatus,
} from 'lib/types/relationship-types';
import { threadTypes } from 'lib/types/thread-types';
import { updateTypes, type UpdateData } from 'lib/types/update-types';
import { cartesianProduct } from 'lib/utils/array';
import { ServerError } from 'lib/utils/errors';

import createThread from '../creators/thread-creator';
import { createUpdates } from '../creators/update-creator';
import { dbQuery, SQL } from '../database/database';
import { fetchFriendRequestRelationshipOperations } from '../fetchers/relationship-fetchers';
import { fetchUserInfos } from '../fetchers/user-fetchers';
import type { Viewer } from '../session/viewer';

async function updateRelationships(
  viewer: Viewer,
  request: RelationshipRequest,
): Promise<RelationshipErrors> {
  const { action } = request;

  if (!viewer.loggedIn) {
    throw new ServerError('not_logged_in');
  }

  const uniqueUserIDs = [...new Set(request.userIDs)];
  const users = await fetchUserInfos(uniqueUserIDs);

  let errors: RelationshipErrors = {};
  const userIDs: string[] = [];
  for (let userID of uniqueUserIDs) {
    if (userID === viewer.userID || !users[userID].username) {
      const acc = errors.invalid_user || [];
      errors.invalid_user = [...acc, userID];
    } else {
      userIDs.push(userID);
    }
  }
  if (!userIDs.length) {
    return errors;
  }

  const updateIDs = [];
  if (action === relationshipActions.FRIEND) {
    // We have to create personal threads before setting the relationship
    // status. By doing that we make sure that failed thread creation is
    // reported to the caller and can be repeated - there should be only
    // one PERSONAL thread per a pair of users and we can safely call it
    // repeatedly.
    await createPersonalThreads(viewer, request);
    const {
      userRelationshipOperations,
      errors: friendRequestErrors,
    } = await fetchFriendRequestRelationshipOperations(viewer, userIDs);
    errors = { ...errors, ...friendRequestErrors };

    const undirectedInsertRows = [];
    const directedInsertRows = [];
    const directedDeleteIDs = [];
    for (const userID in userRelationshipOperations) {
      const operations = userRelationshipOperations[userID];
      const ids = sortIDs(viewer.userID, userID);

      if (operations.length) {
        updateIDs.push(userID);
      }

      for (const operation of operations) {
        if (operation === 'delete_directed') {
          directedDeleteIDs.push(userID);
        } else if (operation === 'friend') {
          const [user1, user2] = ids;
          const status = undirectedStatus.FRIEND;
          undirectedInsertRows.push({ user1, user2, status });
        } else if (operation === 'pending_friend') {
          const status = directedStatus.PENDING_FRIEND;
          directedInsertRows.push([viewer.userID, userID, status]);
        } else if (operation === 'know_of') {
          const [user1, user2] = ids;
          const status = undirectedStatus.KNOW_OF;
          undirectedInsertRows.push({ user1, user2, status });
        } else {
          invariant(false, `unexpected relationship operation ${operation}`);
        }
      }
    }

    const promises = [updateUndirectedRelationships(undirectedInsertRows)];
    if (directedInsertRows.length) {
      const directedInsertQuery = SQL`
        INSERT INTO relationships_directed (user1, user2, status)
        VALUES ${directedInsertRows}
        ON DUPLICATE KEY UPDATE status = VALUES(status)
      `;
      promises.push(dbQuery(directedInsertQuery));
    }
    if (directedDeleteIDs.length) {
      const directedDeleteQuery = SQL`
        DELETE FROM relationships_directed
        WHERE
          (user1 = ${viewer.userID} AND user2 IN (${directedDeleteIDs})) OR
          (status = ${directedStatus.PENDING_FRIEND} AND
            user1 IN (${directedDeleteIDs}) AND user2 = ${viewer.userID})
      `;
      promises.push(dbQuery(directedDeleteQuery));
    }

    await Promise.all(promises);
  } else if (action === relationshipActions.UNFRIEND) {
    updateIDs.push(...userIDs);

    const updateRows = userIDs.map((userID) => {
      const [user1, user2] = sortIDs(viewer.userID, userID);
      return { user1, user2, status: undirectedStatus.KNOW_OF };
    });

    const deleteQuery = SQL`
      DELETE FROM relationships_directed
      WHERE
        status = ${directedStatus.PENDING_FRIEND} AND
        (user1 = ${viewer.userID} AND user2 IN (${userIDs}) OR
          user1 IN (${userIDs}) AND user2 = ${viewer.userID})
    `;

    await Promise.all([
      updateUndirectedRelationships(updateRows, false),
      dbQuery(deleteQuery),
    ]);
  } else if (action === relationshipActions.BLOCK) {
    updateIDs.push(...userIDs);

    const directedRows = [];
    const undirectedRows = [];
    for (const userID of userIDs) {
      directedRows.push([viewer.userID, userID, directedStatus.BLOCKED]);
      const [user1, user2] = sortIDs(viewer.userID, userID);
      undirectedRows.push({ user1, user2, status: undirectedStatus.KNOW_OF });
    }

    const directedInsertQuery = SQL`
      INSERT INTO relationships_directed (user1, user2, status)
      VALUES ${directedRows}
      ON DUPLICATE KEY UPDATE status = VALUES(status)
    `;
    const directedDeleteQuery = SQL`
      DELETE FROM relationships_directed
      WHERE status = ${directedStatus.PENDING_FRIEND} AND
        user1 IN (${userIDs}) AND user2 = ${viewer.userID}
    `;

    await Promise.all([
      dbQuery(directedInsertQuery),
      dbQuery(directedDeleteQuery),
      updateUndirectedRelationships(undirectedRows, false),
    ]);
  } else if (action === relationshipActions.UNBLOCK) {
    updateIDs.push(...userIDs);

    const query = SQL`
      DELETE FROM relationships_directed
      WHERE status = ${directedStatus.BLOCKED} AND
        user1 = ${viewer.userID} AND user2 IN (${userIDs})
    `;
    await dbQuery(query);
  } else {
    invariant(false, `action ${action} is invalid or not supported currently`);
  }

  await createUpdates(
    updateDatasForUserPairs(cartesianProduct([viewer.userID], updateIDs)),
  );

  return errors;
}

function updateDatasForUserPairs(
  userPairs: $ReadOnlyArray<[string, string]>,
): UpdateData[] {
  const time = Date.now();
  const updateDatas = [];
  for (const [user1, user2] of userPairs) {
    updateDatas.push({
      type: updateTypes.UPDATE_USER,
      userID: user1,
      time,
      updatedUserID: user2,
    });
    updateDatas.push({
      type: updateTypes.UPDATE_USER,
      userID: user2,
      time,
      updatedUserID: user1,
    });
  }
  return updateDatas;
}

async function updateUndirectedRelationships(
  changeset: UndirectedRelationshipRow[],
  greatest: boolean = true,
) {
  if (!changeset.length) {
    return;
  }

  const rows = changeset.map((row) => [row.user1, row.user2, row.status]);
  const query = SQL`
    INSERT INTO relationships_undirected (user1, user2, status)
    VALUES ${rows}
  `;
  if (greatest) {
    query.append(
      SQL`ON DUPLICATE KEY UPDATE status = GREATEST(status, VALUES(status))`,
    );
  } else {
    query.append(SQL`ON DUPLICATE KEY UPDATE status = VALUES(status)`);
  }
  await dbQuery(query);
}

async function createPersonalThreads(
  viewer: Viewer,
  request: RelationshipRequest,
) {
  invariant(
    request.action === relationshipActions.FRIEND,
    'We should only create a PERSONAL threads when sending a FRIEND request, ' +
      `but we tried to do that for ${request.action}`,
  );

  const threadCreationPromises = request.userIDs.map((id) =>
    createThread(
      viewer,
      {
        type: threadTypes.PERSONAL,
        initialMemberIDs: [id],
      },
      true,
      'broadcast',
    ),
  );

  return await Promise.all(threadCreationPromises);
}

export {
  updateRelationships,
  updateDatasForUserPairs,
  updateUndirectedRelationships,
};
<commit_msg>[server] Send a message when friend request is sent or accepted<commit_after>// @flow

import invariant from 'invariant';

import { sortIDs } from 'lib/shared/relationship-utils';
import { messageTypes } from 'lib/types/message-types';
import {
  type RelationshipRequest,
  type RelationshipErrors,
  type UndirectedRelationshipRow,
  relationshipActions,
  undirectedStatus,
  directedStatus,
} from 'lib/types/relationship-types';
import { threadTypes } from 'lib/types/thread-types';
import { updateTypes, type UpdateData } from 'lib/types/update-types';
import { cartesianProduct } from 'lib/utils/array';
import { ServerError } from 'lib/utils/errors';
import { promiseAll } from 'lib/utils/promises';
import _mapValues from 'lodash/fp/mapValues';

import createMessages from '../creators/message-creator';
import createThread from '../creators/thread-creator';
import { createUpdates } from '../creators/update-creator';
import { dbQuery, SQL } from '../database/database';
import { fetchFriendRequestRelationshipOperations } from '../fetchers/relationship-fetchers';
import { fetchUserInfos } from '../fetchers/user-fetchers';
import type { Viewer } from '../session/viewer';

async function updateRelationships(
  viewer: Viewer,
  request: RelationshipRequest,
): Promise<RelationshipErrors> {
  const { action } = request;

  if (!viewer.loggedIn) {
    throw new ServerError('not_logged_in');
  }

  const uniqueUserIDs = [...new Set(request.userIDs)];
  const users = await fetchUserInfos(uniqueUserIDs);

  let errors: RelationshipErrors = {};
  const userIDs: string[] = [];
  for (let userID of uniqueUserIDs) {
    if (userID === viewer.userID || !users[userID].username) {
      const acc = errors.invalid_user || [];
      errors.invalid_user = [...acc, userID];
    } else {
      userIDs.push(userID);
    }
  }
  if (!userIDs.length) {
    return errors;
  }

  const updateIDs = [];
  if (action === relationshipActions.FRIEND) {
    // We have to create personal threads before setting the relationship
    // status. By doing that we make sure that failed thread creation is
    // reported to the caller and can be repeated - there should be only
    // one PERSONAL thread per a pair of users and we can safely call it
    // repeatedly.
    const threadIDPerUser = await createPersonalThreads(viewer, request);
    const {
      userRelationshipOperations,
      errors: friendRequestErrors,
    } = await fetchFriendRequestRelationshipOperations(viewer, userIDs);
    errors = { ...errors, ...friendRequestErrors };

    const undirectedInsertRows = [];
    const directedInsertRows = [];
    const directedDeleteIDs = [];
    const messageDatas = [];
    const now = Date.now();
    for (const userID in userRelationshipOperations) {
      const operations = userRelationshipOperations[userID];
      const ids = sortIDs(viewer.userID, userID);

      if (operations.length) {
        updateIDs.push(userID);
      }

      for (const operation of operations) {
        if (operation === 'delete_directed') {
          directedDeleteIDs.push(userID);
        } else if (operation === 'friend') {
          const [user1, user2] = ids;
          const status = undirectedStatus.FRIEND;
          undirectedInsertRows.push({ user1, user2, status });
          messageDatas.push({
            type: messageTypes.UPDATE_RELATIONSHIP,
            threadID: threadIDPerUser[userID],
            creatorID: viewer.userID,
            targetID: userID,
            time: now,
            operation: 'request_accepted',
          });
        } else if (operation === 'pending_friend') {
          const status = directedStatus.PENDING_FRIEND;
          directedInsertRows.push([viewer.userID, userID, status]);
          messageDatas.push({
            type: messageTypes.UPDATE_RELATIONSHIP,
            threadID: threadIDPerUser[userID],
            creatorID: viewer.userID,
            targetID: userID,
            time: now,
            operation: 'request_sent',
          });
        } else if (operation === 'know_of') {
          const [user1, user2] = ids;
          const status = undirectedStatus.KNOW_OF;
          undirectedInsertRows.push({ user1, user2, status });
        } else {
          invariant(false, `unexpected relationship operation ${operation}`);
        }
      }
    }

    const promises = [updateUndirectedRelationships(undirectedInsertRows)];
    if (directedInsertRows.length) {
      const directedInsertQuery = SQL`
        INSERT INTO relationships_directed (user1, user2, status)
        VALUES ${directedInsertRows}
        ON DUPLICATE KEY UPDATE status = VALUES(status)
      `;
      promises.push(dbQuery(directedInsertQuery));
    }
    if (directedDeleteIDs.length) {
      const directedDeleteQuery = SQL`
        DELETE FROM relationships_directed
        WHERE
          (user1 = ${viewer.userID} AND user2 IN (${directedDeleteIDs})) OR
          (status = ${directedStatus.PENDING_FRIEND} AND
            user1 IN (${directedDeleteIDs}) AND user2 = ${viewer.userID})
      `;
      promises.push(dbQuery(directedDeleteQuery));
    }
    if (messageDatas.length > 0) {
      promises.push(createMessages(viewer, messageDatas, 'broadcast'));
    }

    await Promise.all(promises);
  } else if (action === relationshipActions.UNFRIEND) {
    updateIDs.push(...userIDs);

    const updateRows = userIDs.map((userID) => {
      const [user1, user2] = sortIDs(viewer.userID, userID);
      return { user1, user2, status: undirectedStatus.KNOW_OF };
    });

    const deleteQuery = SQL`
      DELETE FROM relationships_directed
      WHERE
        status = ${directedStatus.PENDING_FRIEND} AND
        (user1 = ${viewer.userID} AND user2 IN (${userIDs}) OR
          user1 IN (${userIDs}) AND user2 = ${viewer.userID})
    `;

    await Promise.all([
      updateUndirectedRelationships(updateRows, false),
      dbQuery(deleteQuery),
    ]);
  } else if (action === relationshipActions.BLOCK) {
    updateIDs.push(...userIDs);

    const directedRows = [];
    const undirectedRows = [];
    for (const userID of userIDs) {
      directedRows.push([viewer.userID, userID, directedStatus.BLOCKED]);
      const [user1, user2] = sortIDs(viewer.userID, userID);
      undirectedRows.push({ user1, user2, status: undirectedStatus.KNOW_OF });
    }

    const directedInsertQuery = SQL`
      INSERT INTO relationships_directed (user1, user2, status)
      VALUES ${directedRows}
      ON DUPLICATE KEY UPDATE status = VALUES(status)
    `;
    const directedDeleteQuery = SQL`
      DELETE FROM relationships_directed
      WHERE status = ${directedStatus.PENDING_FRIEND} AND
        user1 IN (${userIDs}) AND user2 = ${viewer.userID}
    `;

    await Promise.all([
      dbQuery(directedInsertQuery),
      dbQuery(directedDeleteQuery),
      updateUndirectedRelationships(undirectedRows, false),
    ]);
  } else if (action === relationshipActions.UNBLOCK) {
    updateIDs.push(...userIDs);

    const query = SQL`
      DELETE FROM relationships_directed
      WHERE status = ${directedStatus.BLOCKED} AND
        user1 = ${viewer.userID} AND user2 IN (${userIDs})
    `;
    await dbQuery(query);
  } else {
    invariant(false, `action ${action} is invalid or not supported currently`);
  }

  await createUpdates(
    updateDatasForUserPairs(cartesianProduct([viewer.userID], updateIDs)),
  );

  return errors;
}

function updateDatasForUserPairs(
  userPairs: $ReadOnlyArray<[string, string]>,
): UpdateData[] {
  const time = Date.now();
  const updateDatas = [];
  for (const [user1, user2] of userPairs) {
    updateDatas.push({
      type: updateTypes.UPDATE_USER,
      userID: user1,
      time,
      updatedUserID: user2,
    });
    updateDatas.push({
      type: updateTypes.UPDATE_USER,
      userID: user2,
      time,
      updatedUserID: user1,
    });
  }
  return updateDatas;
}

async function updateUndirectedRelationships(
  changeset: UndirectedRelationshipRow[],
  greatest: boolean = true,
) {
  if (!changeset.length) {
    return;
  }

  const rows = changeset.map((row) => [row.user1, row.user2, row.status]);
  const query = SQL`
    INSERT INTO relationships_undirected (user1, user2, status)
    VALUES ${rows}
  `;
  if (greatest) {
    query.append(
      SQL`ON DUPLICATE KEY UPDATE status = GREATEST(status, VALUES(status))`,
    );
  } else {
    query.append(SQL`ON DUPLICATE KEY UPDATE status = VALUES(status)`);
  }
  await dbQuery(query);
}

async function createPersonalThreads(
  viewer: Viewer,
  request: RelationshipRequest,
) {
  invariant(
    request.action === relationshipActions.FRIEND,
    'We should only create a PERSONAL threads when sending a FRIEND request, ' +
      `but we tried to do that for ${request.action}`,
  );

  const threadCreationPromises = {};
  for (const userID of request.userIDs) {
    threadCreationPromises[userID] = createThread(
      viewer,
      {
        type: threadTypes.PERSONAL,
        initialMemberIDs: [userID],
      },
      true,
      'broadcast',
    );
  }

  const personalThreadPerUser = await promiseAll(threadCreationPromises);
  return _mapValues((newThread) => newThread.newThreadID)(
    personalThreadPerUser,
  );
}

export {
  updateRelationships,
  updateDatasForUserPairs,
  updateUndirectedRelationships,
};
<|endoftext|>


<commit_before>#!/usr/bin/env node

var fs = require("fs")
var path = require("path")

var parser = require("./parser").generate(function(bi) {
		with(bi) {
			var mbs = maybe("space")
			rule("text", named("text", /^([^@#\$\\]+|\\[^])+/))
			rule("comment", /(^#\*([^\*]+|\*[^#])*\*#)|(^##.+\r?\n?)/)
			rule("space", /^(\s|(^#\*([^\*]+|\*[^#])*\*#)|(^##.+\n?))+/)
			rule("name", named("name", /^[a-zA-Z_][a-zA-Z_0-9]*/))
			rule("number", named("number", /^\d+/))
			rule("string", named("string", and(/^"/, rep(or(
							named("text", /^([^@#\$"\\]+|\\[^])+/),
							"var", "directive", "comment"
						)), /^"/)))
			rule("array", named("array", and(/^\[/, mbs, maybe(join("value", "space")), mbs, /^\]/)))
			rule("hash", named("hash", and(/^\{/, mbs, maybe(join("pair", "space")), mbs, /^\}/)))
			rule("text_value", or("name", "number", "var", "string", "directive"))
			rule("value", or("name", "number", "var", "string", "directive", "array", "hash", "simple_var"))
			rule("pair", named("pair", and("text_value", mbs, /^:/ , mbs, "value")))
			rule("directive", named("directive", and(/^@/, "name", mbs, /^\(/, mbs, named("file_name", /^"([^"\\]|\\[^])+"/), mbs, /^\)/)))
			function arg_list(s, e) {
				with(bi) {
					return named("args", and(s, mbs, maybe(join(or("pair", "value"), "space")), mbs, e))
				}
			}
			rule("index", named("index", and(/^\[/, mbs, "text_value", mbs, /^\]/)))

			rule("filter", and(mbs, /^\|/, mbs, named("filter", "simple_var")))
			var _var = and(maybe("name"),
				rep(or(and(/^\./, "name"), and(mbs, "index"))),
				maybe(named("call", and(mbs, arg_list(/^\(/, /^\)/), maybe(and(mbs, "block"))))),
				rep("filter"));
			rule("simple_var", named("var", and(/^\$/, maybe("name"), rep(or(and(/^./, "name"), and(mbs, "index"))))))
			rule("var", named("var", and(/^\$\{/, _var, /^\}/)))
			rule("block", named("block", and(maybe(arg_list(/^\|/, /^\|/)), mbs, /^#/, and(rep(or("text", "comment", "var", "directive")), /^#end/))))
			main_rule(rep(or("text", "comment", "var", "directive")))
		}
	})

var includes = {}
var parsed = {}

function include_file(file_name) {
	if(!includes[file_name])
		includes[file_name] = fs.readFileSync(file_name).toString("UTF-8")
}

function parse_file(file_name) {
	if(parsed[file_name])
		return;
	function unescape_str(str) {
		return str.replace(/\\[^]/g, function(m) {
				return m.substring(1, 2)
			})
	}
	function get_val(nd) {
		var c = nd.children || []
		var r = "$ctx"
		for(var i=0; i<c.length; i++)
			if(c[i].name!='call' && c[i].name!='filter')
				r = ["var_index(", r, ", ", val_node(c[i]), ")"].join("")
		return r
	}
	function val_node(nd) {
		if(nd.name=="name") {
			return JSON.stringify(nd.value())
		} else if(nd.name=="string") {
			var c = nd.children
			if(!c)
				return "''"
			else if(c.length==1)
				return val_node(c[0])
			else {
				var r = ["(function() {\nvar $out = [];\n"]
				for(var i=0; i<c.length; i++)
					r.push(txt_node(c[i]))
				r.push("return $out.join('');\n})()")
				return r.join("")
			}
		} else if(nd.name=="number") {
			return nd.value()
		} else if(nd.name=="text") {
			return JSON.stringify(unescape_str(nd.value()))
		} else if(nd.name=="array") {
			var c = nd.children || []
			var r = []
			for(var i=0; i<c.length; i++)
				r.push(val_node(c[i]))
			return "[" + r.join(", ") + "]"
		} else if(nd.name=="hash") {
			if(!nd.children)
				return "{}"
			var c = nd.children
			var r = ["(function() {\nvar r = {};\n"]
			for(var i=0; i<c.length; i++) {
				r.push("r[")
				r.push(val_node(c[i].children[0]))
				r.push("] = ")
				r.push(val_node(c[i].children[1]))
				r.push(";\n")
			}
			r.push("return r;\n})()")
			return r.join("")
		} else if(nd.name=="index") {
			return val_node(nd.children[0])
		} else if(nd.name=="var") {
			if(!nd.children)
				return "$ctx"
			else {
				for(var i=0; i<nd.children.length; i++) {
					var nm = nd.children[i].name
					if(nm=="call" || nm=="filter") {
						return ["(function() {\nvar $out = [];\n", txt_node(nd), "return $out.join('');\n})()"].join("")
					}
				}
				return get_val(nd)
			}
		} else if(nd.name=="directive") {
			var c = nd.children
			var nm = path.join(path.dirname(file_name), unescape_str(c[1].value().replace(/^"|"$/g, "")))
			if(c[0].value()=="include") {
				include_file(nm)
				return ["includes[", JSON.stringify(nm), "]"].join("")
			} else if(c[0].value()=="parse") {
				parse_file(nm)
				return ["(function() {\nvar $out = [];\nparsed[", JSON.stringify(nm), "]($ctx, $out);\nreturn $out.join('');\n})()"].join("")
			} else
				throw new Error("Unknown directive: " + JSON.stringify())
		} else if(nd.name=="block") {
			var c = nd.children || []
			var r = ["(function(ctx) {\nreturn function($unused, $args, $out) {\nvar $ctx = context(ctx);\n"]
			for(var i=0; i<c.length; i++) {
				if(c[i].name=="args") {
					var al = c[i].children || []
					for(var j=0; j<al.length; j++) {
						if(al[j].name=="pair") {
							var nm = val_node(al[j].children[0])
							r.push("$ctx[")
							r.push(nm)
							r.push("] = $args[")
							r.push(j)
							r.push("] || $args[")
							r.push(nm)
							r.push("] || ")
							r.push(val_node(al[j].children[1]))
							r.push(";\n")
						} else {
							var nm = val_node(al[j])
							r.push("$ctx[")
							r.push(nm)
							r.push("] = $args[")
							r.push(j)
							r.push("] || $args[")
							r.push(nm)
							r.push("];\n")
						}
					}
				} else
					r.push(txt_node(c[i]))
			}
			r.push("};\n})($ctx)")
			return r.join("")
		}
	}
	function txt_node(nd) {
		if(nd instanceof Array) {
			var r = []
			for(var i=0; i<nd.length; i++)
				r.push(txt_node(nd[i]))
			return r.join("")
		} else if(nd.name=="directive" && nd.children[0].value()=="parse") {
			var c = nd.children
			var nm = path.join(path.dirname(file_name), unescape_str(c[1].value().replace(/^"|"$/g, "")))
			parse_file(nm)
			return ["parsed[", JSON.stringify(nm), "]($ctx, $out);\n"].join("")
		} else if(nd.name=="var" && nd.children && (nd.children[nd.children.length-1].name=="call" || nd.children[nd.children.length-1].name=="filter")) {
			var fn = get_val(nd)
			var cl = []
			var call_used = false
			for(var i=0; i<nd.children.length; i++) {
				if(nd.children[i].name=="call") {
					cl = nd.children[i].children
					call_used = true
				}
			}
			var r = []
			if(call_used) {
				r = ["(function() {\nvar args = {};\n"]
				if(cl.length>=1) {
					var al = cl[0].children || []
					for(var j=0; j<al.length; j++) {
						if(al[j].name=="pair") {
							r.push("args[")
							r.push(val_node(al[j].children[0]))
							r.push("] = ")
							r.push(val_node(al[j].children[1]))
							r.push(";\n")
						} else {
							r.push("args[")
							r.push(j)
							r.push("] = ")
							r.push(val_node(al[j]))
							r.push(";\n")
						}
					}
				}
				if(cl.length>=2) {
					r.push("args['yield'] = ")
					r.push(val_node(cl[1]))
					r.push(";\n")
				}
				r.push("var_call(")
				r.push(fn)
				r.push(", $ctx, args, $out);\n})();\n")
				r = r.join("")
			} else
				r = get_val(nd)
			for(var i=0; i<nd.children.length; i++) {
				if(nd.children[i].name=="filter") {
					if(call_used) {
						r = ["var_call(",
							get_val(nd.children[i].children[0]),
							", $ctx, [(function() {\nvar $out=[];\n", r, "return $out.join('');\n})()], $out);\n"].join("")
					} else {
						call_used = true
						r = ["var_call(",
							get_val(nd.children[i].children[0]),
							", $ctx, [", r, "], $out);\n"].join("")
					}
				}
			}
			return r
		} else
			return ["$out.push(", val_node(nd), ");\n"].join("")
	}
	var tree = null
	try {
		tree = parser(fs.readFileSync(file_name).toString("UTF-8"))
	} catch(e) {
		throw new Error(file_name + e.message)
	}
	parsed[file_name] = "function($ctx, $out) {\n" + txt_node(tree) + "}\n"
}

var out = function(s) {
		process.stdout.write(s)
	}
var fn_name = "tpl2js"

for(var i=2; i<process.argv.length; i++) {
	if(process.argv[i]=="-o") {
		i++
		out = (function(fname) {
				return function(s) {
						fs.writeFileSync(fname, s)
					}
			})(process.argv[i])
	} else if(process.argv[i]=="-fn") {
		i++;
		fn_name = process.argv[i]
	} else
		parse_file(process.argv[i])
}

var r = ["function ", fn_name, "(file_name, ctx) {\n"]

r.push("function context() { if(arguments.length) { var rfn = function(){}; rfn.prototype = arguments[0]; return new rfn(); } else return {}; };\n")
r.push("function var_index(v, i) { return v ? v[i] : null; };\n")
r.push("function var_call(v, ctx, args, out) { if(v && typeof v=='function') v(ctx, args, out); };\n")

r.push("var $ctx = context(ctx);\n")

function get_plugins(dir) {
	var p = arguments[1] || []
	var plugins = fs.readdirSync(dir)
	function pth(px) {
		for(var j in px) {
			r.push("[")
			r.push(JSON.stringify(px[j]))
			r.push("]")
		}
	}
	for(var i in plugins) {
		var nm = path.join(dir, plugins[i])
		if(nm==".." || nm==".") {
			//skip
		} else if(fs.statSync(nm).isDirectory()) {
			var np = p.concat([plugins[i]])
			r.push("$ctx")
			pth(np)
			r.push(" = {};\n")
			get_plugins(nm, np)
		} else {
			r.push("$ctx")
			pth(p.concat([plugins[i].replace(/\.js$/, "")]))
			r.push(" = ")
			r.push(fs.readFileSync(nm))
			r.push(";\n")
		}
	}
}

get_plugins(path.join(__dirname, "plugins"))

r.push("var includes = ")
r.push(JSON.stringify(includes))
r.push(";\n")

r.push("var parsed = {\n")
var fst = true
for(var i in parsed) {
	if(!fst)
		r.push(",\n")
	fst = false
	r.push(JSON.stringify(i))
	r.push(": ")
	r.push(parsed[i])
}
r.push("\n};\n")

r.push("if(!parsed[file_name])\nthrow new Error('File not found: \"' + file_name + '\"');\n")
r.push("var $out = [];\n")
r.push("parsed[file_name]($ctx, $out);\n")
r.push("return $out.join('');\n")
r.push("}\n")

if(r.length)
	out(r.join(""))

<commit_msg>Bug with "0" as argument fixed.<commit_after>#!/usr/bin/env node

var fs = require("fs")
var path = require("path")

var parser = require("./parser").generate(function(bi) {
		with(bi) {
			var mbs = maybe("space")
			rule("text", named("text", /^([^@#\$\\]+|\\[^])+/))
			rule("comment", /(^#\*([^\*]+|\*[^#])*\*#)|(^##.+\r?\n?)/)
			rule("space", /^(\s|(^#\*([^\*]+|\*[^#])*\*#)|(^##.+\n?))+/)
			rule("name", named("name", /^[a-zA-Z_][a-zA-Z_0-9]*/))
			rule("number", named("number", /^\d+/))
			rule("string", named("string", and(/^"/, rep(or(
							named("text", /^([^@#\$"\\]+|\\[^])+/),
							"var", "directive", "comment"
						)), /^"/)))
			rule("array", named("array", and(/^\[/, mbs, maybe(join("value", "space")), mbs, /^\]/)))
			rule("hash", named("hash", and(/^\{/, mbs, maybe(join("pair", "space")), mbs, /^\}/)))
			rule("text_value", or("name", "number", "var", "string", "directive"))
			rule("value", or("name", "number", "var", "string", "directive", "array", "hash", "simple_var"))
			rule("pair", named("pair", and("text_value", mbs, /^:/ , mbs, "value")))
			rule("directive", named("directive", and(/^@/, "name", mbs, /^\(/, mbs, named("file_name", /^"([^"\\]|\\[^])+"/), mbs, /^\)/)))
			function arg_list(s, e) {
				with(bi) {
					return named("args", and(s, mbs, maybe(join(or("pair", "value"), "space")), mbs, e))
				}
			}
			rule("index", named("index", and(/^\[/, mbs, "text_value", mbs, /^\]/)))

			rule("filter", and(mbs, /^\|/, mbs, named("filter", "simple_var")))
			var _var = and(maybe("name"),
				rep(or(and(/^\./, "name"), and(mbs, "index"))),
				maybe(named("call", and(mbs, arg_list(/^\(/, /^\)/), maybe(and(mbs, "block"))))),
				rep("filter"));
			rule("simple_var", named("var", and(/^\$/, maybe("name"), rep(or(and(/^./, "name"), and(mbs, "index"))))))
			rule("var", named("var", and(/^\$\{/, _var, /^\}/)))
			rule("block", named("block", and(maybe(arg_list(/^\|/, /^\|/)), mbs, /^#/, and(rep(or("text", "comment", "var", "directive")), /^#end/))))
			main_rule(rep(or("text", "comment", "var", "directive")))
		}
	})

var includes = {}
var parsed = {}

function include_file(file_name) {
	if(!includes[file_name])
		includes[file_name] = fs.readFileSync(file_name).toString("UTF-8")
}

function parse_file(file_name) {
	if(parsed[file_name])
		return;
	function unescape_str(str) {
		return str.replace(/\\[^]/g, function(m) {
				return m.substring(1, 2)
			})
	}
	function get_val(nd) {
		var c = nd.children || []
		var r = "$ctx"
		for(var i=0; i<c.length; i++)
			if(c[i].name!='call' && c[i].name!='filter')
				r = ["var_index(", r, ", ", val_node(c[i]), ")"].join("")
		return r
	}
	function val_node(nd) {
		if(nd.name=="name") {
			return JSON.stringify(nd.value())
		} else if(nd.name=="string") {
			var c = nd.children
			if(!c)
				return "''"
			else if(c.length==1)
				return val_node(c[0])
			else {
				var r = ["(function() {\nvar $out = [];\n"]
				for(var i=0; i<c.length; i++)
					r.push(txt_node(c[i]))
				r.push("return $out.join('');\n})()")
				return r.join("")
			}
		} else if(nd.name=="number") {
			return nd.value()
		} else if(nd.name=="text") {
			return JSON.stringify(unescape_str(nd.value()))
		} else if(nd.name=="array") {
			var c = nd.children || []
			var r = []
			for(var i=0; i<c.length; i++)
				r.push(val_node(c[i]))
			return "[" + r.join(", ") + "]"
		} else if(nd.name=="hash") {
			if(!nd.children)
				return "{}"
			var c = nd.children
			var r = ["(function() {\nvar r = {};\n"]
			for(var i=0; i<c.length; i++) {
				r.push("r[")
				r.push(val_node(c[i].children[0]))
				r.push("] = ")
				r.push(val_node(c[i].children[1]))
				r.push(";\n")
			}
			r.push("return r;\n})()")
			return r.join("")
		} else if(nd.name=="index") {
			return val_node(nd.children[0])
		} else if(nd.name=="var") {
			if(!nd.children)
				return "$ctx"
			else {
				for(var i=0; i<nd.children.length; i++) {
					var nm = nd.children[i].name
					if(nm=="call" || nm=="filter") {
						return ["(function() {\nvar $out = [];\n", txt_node(nd), "return $out.join('');\n})()"].join("")
					}
				}
				return get_val(nd)
			}
		} else if(nd.name=="directive") {
			var c = nd.children
			var nm = path.join(path.dirname(file_name), unescape_str(c[1].value().replace(/^"|"$/g, "")))
			if(c[0].value()=="include") {
				include_file(nm)
				return ["includes[", JSON.stringify(nm), "]"].join("")
			} else if(c[0].value()=="parse") {
				parse_file(nm)
				return ["(function() {\nvar $out = [];\nparsed[", JSON.stringify(nm), "]($ctx, $out);\nreturn $out.join('');\n})()"].join("")
			} else
				throw new Error("Unknown directive: " + JSON.stringify())
		} else if(nd.name=="block") {
			var c = nd.children || []
			var r = ["(function(ctx) {\nreturn function($unused, $args, $out) {\nvar $ctx = context(ctx);\n"]
			for(var i=0; i<c.length; i++) {
				if(c[i].name=="args") {
					var al = c[i].children || []
					for(var j=0; j<al.length; j++) {
						if(al[j].name=="pair") {
							var nm = val_node(al[j].children[0])
							r.push("$ctx[" + nm + "] = $args[" + j + "]===undefined ? ($args[" + nm + "]===undefined ? " + val_node(al[j].children[1]) + " : $args[" + nm + "]) : $args[" + j + "];")
						} else {
							var nm = val_node(al[j])
							r.push("$ctx[" + nm + "] = $args[" + j + "]===undefined ? ($args[" + nm + "]===undefined ? null : $args[" + nm + "]) : $args[" + j + "];")
						}
					}
				} else
					r.push(txt_node(c[i]))
			}
			r.push("};\n})($ctx)")
			return r.join("")
		}
	}
	function txt_node(nd) {
		if(nd instanceof Array) {
			var r = []
			for(var i=0; i<nd.length; i++)
				r.push(txt_node(nd[i]))
			return r.join("")
		} else if(nd.name=="directive" && nd.children[0].value()=="parse") {
			var c = nd.children
			var nm = path.join(path.dirname(file_name), unescape_str(c[1].value().replace(/^"|"$/g, "")))
			parse_file(nm)
			return ["parsed[", JSON.stringify(nm), "]($ctx, $out);\n"].join("")
		} else if(nd.name=="var" && nd.children && (nd.children[nd.children.length-1].name=="call" || nd.children[nd.children.length-1].name=="filter")) {
			var fn = get_val(nd)
			var cl = []
			var call_used = false
			for(var i=0; i<nd.children.length; i++) {
				if(nd.children[i].name=="call") {
					cl = nd.children[i].children
					call_used = true
				}
			}
			var r = []
			if(call_used) {
				r = ["(function() {\nvar args = {};\n"]
				if(cl.length>=1) {
					var al = cl[0].children || []
					for(var j=0; j<al.length; j++) {
						if(al[j].name=="pair") {
							r.push("args[")
							r.push(val_node(al[j].children[0]))
							r.push("] = ")
							r.push(val_node(al[j].children[1]))
							r.push(";\n")
						} else {
							r.push("args[")
							r.push(j)
							r.push("] = ")
							r.push(val_node(al[j]))
							r.push(";\n")
						}
					}
				}
				if(cl.length>=2) {
					r.push("args['yield'] = ")
					r.push(val_node(cl[1]))
					r.push(";\n")
				}
				r.push("var_call(")
				r.push(fn)
				r.push(", $ctx, args, $out);\n})();\n")
				r = r.join("")
			} else
				r = get_val(nd)
			for(var i=0; i<nd.children.length; i++) {
				if(nd.children[i].name=="filter") {
					if(call_used) {
						r = ["var_call(",
							get_val(nd.children[i].children[0]),
							", $ctx, [(function() {\nvar $out=[];\n", r, "return $out.join('');\n})()], $out);\n"].join("")
					} else {
						call_used = true
						r = ["var_call(",
							get_val(nd.children[i].children[0]),
							", $ctx, [", r, "], $out);\n"].join("")
					}
				}
			}
			return r
		} else
			return ["$out.push(", val_node(nd), ");\n"].join("")
	}
	var tree = null
	try {
		tree = parser(fs.readFileSync(file_name).toString("UTF-8"))
	} catch(e) {
		throw new Error(file_name + e.message)
	}
	parsed[file_name] = "function($ctx, $out) {\n" + txt_node(tree) + "}\n"
}

var out = function(s) {
		process.stdout.write(s)
	}
var fn_name = "tpl2js"

for(var i=2; i<process.argv.length; i++) {
	if(process.argv[i]=="-o") {
		i++
		out = (function(fname) {
				return function(s) {
						fs.writeFileSync(fname, s)
					}
			})(process.argv[i])
	} else if(process.argv[i]=="-fn") {
		i++;
		fn_name = process.argv[i]
	} else
		parse_file(process.argv[i])
}

var r = ["function ", fn_name, "(file_name, ctx) {\n"]

r.push("function context() { if(arguments.length) { var rfn = function(){}; rfn.prototype = arguments[0]; return new rfn(); } else return {}; };\n")
r.push("function var_index(v, i) { return v ? v[i] : null; };\n")
r.push("function var_call(v, ctx, args, out) { if(v && typeof v=='function') v(ctx, args, out); };\n")

r.push("var $ctx = context(ctx);\n")

function get_plugins(dir) {
	var p = arguments[1] || []
	var plugins = fs.readdirSync(dir)
	function pth(px) {
		for(var j in px) {
			r.push("[")
			r.push(JSON.stringify(px[j]))
			r.push("]")
		}
	}
	for(var i in plugins) {
		var nm = path.join(dir, plugins[i])
		if(nm==".." || nm==".") {
			//skip
		} else if(fs.statSync(nm).isDirectory()) {
			var np = p.concat([plugins[i]])
			r.push("$ctx")
			pth(np)
			r.push(" = {};\n")
			get_plugins(nm, np)
		} else {
			r.push("$ctx")
			pth(p.concat([plugins[i].replace(/\.js$/, "")]))
			r.push(" = ")
			r.push(fs.readFileSync(nm))
			r.push(";\n")
		}
	}
}

get_plugins(path.join(__dirname, "plugins"))

r.push("var includes = ")
r.push(JSON.stringify(includes))
r.push(";\n")

r.push("var parsed = {\n")
var fst = true
for(var i in parsed) {
	if(!fst)
		r.push(",\n")
	fst = false
	r.push(JSON.stringify(i))
	r.push(": ")
	r.push(parsed[i])
}
r.push("\n};\n")

r.push("if(!parsed[file_name])\nthrow new Error('File not found: \"' + file_name + '\"');\n")
r.push("var $out = [];\n")
r.push("parsed[file_name]($ctx, $out);\n")
r.push("return $out.join('');\n")
r.push("}\n")

if(r.length)
	out(r.join(""))

<|endoftext|>


<commit_before>/* global mkdir, exec */

require('shelljs/global');

mkdir('-p', '.build/js');
exec('browserify lib/client.js --standalone FamodulusClient -o .build/js/famodulus.browser.js');
exec('jsdoc -d .build/doc lib/*');
<commit_msg>added license<commit_after>/*
 * Copyright 2016 Pascal Mainini
 * Licensed under MIT license, see included file LICENSE or
 * http://opensource.org/licenses/MIT
 */
/* global mkdir, exec */

require('shelljs/global');

mkdir('-p', '.build/js');
exec('browserify lib/client.js --standalone FamodulusClient -o .build/js/famodulus.browser.js');
exec('jsdoc -d .build/doc lib/*');
<|endoftext|>


<commit_before>
<commit_msg>添加浏览器嗅探代码<commit_after>/**
 * 浏览器判断模块
 * @file
 * @module KS.browser
 * @since 1.0
 */

/**
 * 提供浏览器检测的模块
 * @unfile
 * @module KS.browser
 */
var browser = KS.browser = function() {
    var agent = navigator.userAgent.toLowerCase(),
        opera = window.opera,
        browser = {
            /**
             * @property {boolean} ie 检测当前浏览器是否为IE
             * @example
             * ```javascript
             * if ( KS.browser.ie ) {
             *     console.log( '当前浏览器是IE' );
             * }
             * ```
             */
            ie: /(msie\s|trident.*rv:)([\w.]+)/.test(agent),

            /**
             * @property {boolean} opera 检测当前浏览器是否为Opera
             * @example
             * ```javascript
             * if ( KS.browser.opera ) {
             *     console.log( '当前浏览器是Opera' );
             * }
             * ```
             */
            opera: (!!opera && opera.version),

            /**
             * @property {boolean} webkit 检测当前浏览器是否是webkit内核的浏览器
             * @example
             * ```javascript
             * if ( KS.browser.webkit ) {
             *     console.log( '当前浏览器是webkit内核浏览器' );
             * }
             * ```
             */
            webkit: (agent.indexOf(' applewebkit/') > -1),

            /**
             * @property {boolean} mac 检测当前浏览器是否是运行在mac平台下
             * @example
             * ```javascript
             * if ( KS.browser.mac ) {
             *     console.log( '当前浏览器运行在mac平台下' );
             * }
             * ```
             */
            mac: (agent.indexOf('macintosh') > -1),

            /**
             * @property {boolean} quirks 检测当前浏览器是否处于“怪异模式”下
             * @example
             * ```javascript
             * if ( KS.browser.quirks ) {
             *     console.log( '当前浏览器运行处于“怪异模式”' );
             * }
             * ```
             */
            quirks: (document.compatMode == 'BackCompat')
        };

    /**
     * @property {boolean} gecko 检测当前浏览器内核是否是gecko内核
     * @example
     * ```javascript
     * if ( KS.browser.gecko ) {
     *     console.log( '当前浏览器内核是gecko内核' );
     * }
     * ```
     */
    browser.gecko = (navigator.product == 'Gecko' && !browser.webkit && !browser.opera && !browser.ie);

    var version = 0;

    // Internet Explorer 6.0+
    if (browser.ie) {

        var v1 = agent.match(/(?:msie\s([\w.]+))/);
        var v2 = agent.match(/(?:trident.*rv:([\w.]+))/);
        if (v1 && v2 && v1[1] && v2[1]) {
            version = Math.max(v1[1] * 1, v2[1] * 1);
        } else if (v1 && v1[1]) {
            version = v1[1] * 1;
        } else if (v2 && v2[1]) {
            version = v2[1] * 1;
        } else {
            version = 0;
        }

        browser.ie11Compat = document.documentMode == 11;
        /**
         * @property { boolean } ie9Compat 检测浏览器模式是否为 IE9 兼容模式
         * @warning 如果浏览器不是IE， 则该值为undefined
         * @example
         * ```javascript
         * if ( KS.browser.ie9Compat ) {
         *     console.log( '当前浏览器运行在IE9兼容模式下' );
         * }
         * ```
         */
        browser.ie9Compat = document.documentMode == 9;

        /**
         * @property { boolean } ie8 检测浏览器是否是IE8浏览器
         * @warning 如果浏览器不是IE， 则该值为undefined
         * @example
         * ```javascript
         * if ( KS.browser.ie8 ) {
         *     console.log( '当前浏览器是IE8浏览器' );
         * }
         * ```
         */
        browser.ie8 = !!document.documentMode;

        /**
         * @property { boolean } ie8Compat 检测浏览器模式是否为 IE8 兼容模式
         * @warning 如果浏览器不是IE， 则该值为undefined
         * @example
         * ```javascript
         * if ( KS.browser.ie8Compat ) {
         *     console.log( '当前浏览器运行在IE8兼容模式下' );
         * }
         * ```
         */
        browser.ie8Compat = document.documentMode == 8;

        /**
         * @property { boolean } ie7Compat 检测浏览器模式是否为 IE7 兼容模式
         * @warning 如果浏览器不是IE， 则该值为undefined
         * @example
         * ```javascript
         * if ( KS.browser.ie7Compat ) {
         *     console.log( '当前浏览器运行在IE7兼容模式下' );
         * }
         * ```
         */
        browser.ie7Compat = ((version == 7 && !document.documentMode) ||
            document.documentMode == 7);

        /**
         * @property { boolean } ie6Compat 检测浏览器模式是否为 IE6 模式 或者怪异模式
         * @warning 如果浏览器不是IE， 则该值为undefined
         * @example
         * ```javascript
         * if ( KS.browser.ie6Compat ) {
         *     console.log( '当前浏览器运行在IE6模式或者怪异模式下' );
         * }
         * ```
         */
        browser.ie6Compat = (version < 7 || browser.quirks);

        browser.ie9above = version > 8;

        browser.ie9below = version < 9;

        browser.ie11above = version > 10;

        browser.ie11below = version < 11;

    }

    // Gecko.
    if (browser.gecko) {
        var geckoRelease = agent.match(/rv:([\d\.]+)/);
        if (geckoRelease) {
            geckoRelease = geckoRelease[1].split('.');
            version = geckoRelease[0] * 10000 + (geckoRelease[1] || 0) * 100 + (geckoRelease[2] || 0) * 1;
        }
    }

    /**
     * @property { Number } chrome 检测当前浏览器是否为Chrome, 如果是，则返回Chrome的大版本号
     * @warning 如果浏览器不是chrome， 则该值为undefined
     * @example
     * ```javascript
     * if ( KS.browser.chrome ) {
     *     console.log( '当前浏览器是Chrome' );
     * }
     * ```
     */
    if (/chrome\/(\d+\.\d)/i.test(agent)) {
        browser.chrome = +RegExp['\x241'];
    }

    /**
     * @property { Number } safari 检测当前浏览器是否为Safari, 如果是，则返回Safari的大版本号
     * @warning 如果浏览器不是safari， 则该值为undefined
     * @example
     * ```javascript
     * if ( KS.browser.safari ) {
     *     console.log( '当前浏览器是Safari' );
     * }
     * ```
     */
    if (/(\d+\.\d)?(?:\.\d)?\s+safari\/?(\d+\.\d+)?/i.test(agent) && !/chrome/i.test(agent)) {
        browser.safari = +(RegExp['\x241'] || RegExp['\x242']);
    }

    // Opera 9.50+
    if (browser.opera)
        version = parseFloat(opera.version());

    // WebKit 522+ (Safari 3+)
    if (browser.webkit)
        version = parseFloat(agent.match(/ applewebkit\/(\d+)/)[1]);

    /**
     * @property { Number } version 检测当前浏览器版本号
     * @remind
     *

     *
IE系列返回值为5,6,7,8,9,10等

     *
gecko系列会返回10900，158900等

     *
webkit系列会返回其build号 (如 522等)

     *

     * @example
     * ```javascript
     * console.log( '当前浏览器版本号是： ' + KS.browser.version );
     * ```
     */
    browser.version = version;

    /**
     * @property { boolean } isCompatible 检测当前浏览器是否能够与KS良好兼容
     * @example
     * ```javascript
     * if ( KS.browser.isCompatible ) {
     *     console.log( '浏览器与KS能够良好兼容' );
     * }
     * ```
     */
    /* browser.isCompatible = !browser.mobile && (
         (browser.ie && version >= 6) ||
         (browser.gecko && version >= 10801) ||
         (browser.opera && version >= 9.5) ||
         (browser.air && version >= 1) ||
         (browser.webkit && version >= 522) ||
         false);*/
    return browser;
}();
//快捷方式
var ie = browser.ie,
    webkit = browser.webkit,
    gecko = browser.gecko,
    opera = browser.opera;<|endoftext|>


<commit_before><commit_msg>Fix bugs.(CBack to ES5)<commit_after><|endoftext|>


<commit_before>const chalk = require("chalk"),
  os = require("os"),
  env = requireModule("env"),
  gulp = requireModule("gulp-with-help"),
  gulpDebugGenerator = require("gulp-debug"),
  debug = require("debug")("build"),
  promisifyStream = requireModule("promisify"),
  dotnetCli = require("gulp-dotnet-cli"),
  dotnetClean = dotnetCli.clean,
  dotnetBuild = dotnetCli.build,
  throwIfNoFiles = requireModule("throw-if-no-files"),
  xbuild = requireModule("gulp-xbuild"),
  gutil = requireModule("gulp-util"),
  log = requireModule("log"),
  resolveMasks = requireModule("resolve-masks"),
  logConfig = requireModule("log-config"),
  msbuild = require("gulp-msbuild");

function gulpDebug(title) {
    return gulpDebugGenerator({
            title,
            logger: debug
    });
}

gulp.task("prebuild", ["nuget-restore"]);

const myTasks = ["build"],
  myVars = [
    "BUILD_CONFIGURATION",
    "BUILD_PLATFORM",
    "BUILD_ARCHITECTURE",
    "BUILD_TARGETS",
    "BUILD_TOOLSVERSION",
    "BUILD_VERBOSITY",
    "BUILD_MSBUILD_NODE_REUSE",
    "BUILD_MAX_CPUCOUNT",
    "MAX_CONCURRENCY",
    "BUILD_INCLUDE",
    "BUILD_EXCLUDE",
    "BUILD_ADDITIONAL_EXCLUDE",
    "BUILD_SHOW_INFO",
    "BUILD_FAIL_ON_ERROR"
  ];
env.associate(myVars, myTasks);

gulp.task(
  "build",
  "Builds Visual Studio solutions in tree",
  ["prebuild"],
  build
);

gulp.task("quick-build", "Quick build without pre-cursors", build);

async function build() {
  const slnMasks = resolveMasks("BUILD_INCLUDE", [ "BUILD_EXCLUDE", "BUILD_ADDITIONAL_EXCLUDE"]);
  debug({
    slnMasks
  });
  const solutions = gulp
    .src(slnMasks, { allowEmpty: true })
    .pipe(
      gulpDebug({
        title: "build-sln",
      })
    )/
    .pipe(throwIfNoFiles(`No solutions found matching masks: ${slnMasks}}`));

  // TODO: find a reliable, quick way to determine if the projects to be compiled
  //       are all dotnet core -- trawling *.csproj is slow and has caused hangups
  //       here, so for now, DNC build must be requested via env DONET_CORE
  return env.resolveFlag("DOTNET_CORE")
    ? buildForNetCore(solutions)
    : buildForNETFramework(solutions);
}

function buildForNetCore(solutions) {
  log.info(gutil.colors.yellow("Building with dotnet core"));
  const configuration = env.resolve("BUILD_CONFIGURATION");
  return promisifyStream(
    solutions
      .pipe(
        dotnetClean({
          configuration
        })
      )
      .pipe(
        dotnetBuild({
          verbosity: "minimal",
          configuration
        })
      )
  );
}

function buildForNETFramework(solutions) {
  log.info(chalk.yellowBright("Building with full .NET framework"));
  return promisifyStream(buildAsStream(solutions));
}

function buildAsStream(solutions) {
  const builder = os.platform() === "win32" ? msbuild : xbuild;
  const config = {
    toolsVersion: env.resolve("BUILD_TOOLSVERSION"),
    targets: env.resolveArray("BUILD_TARGETS"),
    configuration: env.resolve("BUILD_CONFIGURATION"),
    stdout: true,
    verbosity: env.resolve("BUILD_VERBOSITY"),
    errorOnFail: env.resolveFlag("BUILD_FAIL_ON_ERROR"),
    solutionPlatform: env.resolve("BUILD_PLATFORM"),
    // NB: this is the MSBUILD architecture, NOT your desired output architecture
    architecture: env.resolve("BUILD_ARCHITECTURE"),
    nologo: false,
    logCommand: true,
    nodeReuse: env.resolveFlag("BUILD_MSBUILD_NODE_REUSE"),
    maxcpucount: env.resolveNumber("BUILD_MAX_CPU_COUNT")
  };

  if (env.resolveFlag("BUILD_SHOW_INFO")) {
    logConfig(config, {
      toolsVersion: "Tools version",
      targets: "Build targets",
      configuration: "Build configuration",
      stdout: "Log to stdout",
      verbosity: "Build verbosity",
      errorOnFail: "Any error fails the build",
      solutionPlatform: "Build platform",
      architecture: "Build architecture",
      nodeReuse: "Re-use MSBUILD nodes",
      maxcpucount: "Max CPUs to use for build",
    });
  }
  return solutions
    .pipe(gulpDebug({ title: "before msbuild" }))
    .pipe(builder(config))
    .on("end", function() {
      gulpDebug({ title: "solutions pipe ends" });
    })
    .pipe(gulpDebug({ title: "after msbuild" }));
}
<commit_msg>:bug: rogue /<commit_after>const chalk = require("chalk"),
  os = require("os"),
  env = requireModule("env"),
  gulp = requireModule("gulp-with-help"),
  gulpDebugGenerator = require("gulp-debug"),
  debug = require("debug")("build"),
  promisifyStream = requireModule("promisify"),
  dotnetCli = require("gulp-dotnet-cli"),
  dotnetClean = dotnetCli.clean,
  dotnetBuild = dotnetCli.build,
  throwIfNoFiles = requireModule("throw-if-no-files"),
  xbuild = requireModule("gulp-xbuild"),
  gutil = requireModule("gulp-util"),
  log = requireModule("log"),
  resolveMasks = requireModule("resolve-masks"),
  logConfig = requireModule("log-config"),
  msbuild = require("gulp-msbuild");

function gulpDebug(title) {
    return gulpDebugGenerator({
            title,
            logger: debug
    });
}

gulp.task("prebuild", ["nuget-restore"]);

const myTasks = ["build"],
  myVars = [
    "BUILD_CONFIGURATION",
    "BUILD_PLATFORM",
    "BUILD_ARCHITECTURE",
    "BUILD_TARGETS",
    "BUILD_TOOLSVERSION",
    "BUILD_VERBOSITY",
    "BUILD_MSBUILD_NODE_REUSE",
    "BUILD_MAX_CPUCOUNT",
    "MAX_CONCURRENCY",
    "BUILD_INCLUDE",
    "BUILD_EXCLUDE",
    "BUILD_ADDITIONAL_EXCLUDE",
    "BUILD_SHOW_INFO",
    "BUILD_FAIL_ON_ERROR"
  ];
env.associate(myVars, myTasks);

gulp.task(
  "build",
  "Builds Visual Studio solutions in tree",
  ["prebuild"],
  build
);

gulp.task("quick-build", "Quick build without pre-cursors", build);

async function build() {
  const slnMasks = resolveMasks("BUILD_INCLUDE", [ "BUILD_EXCLUDE", "BUILD_ADDITIONAL_EXCLUDE"]);
  debug({
    slnMasks
  });
  const solutions = gulp
    .src(slnMasks, { allowEmpty: true })
    .pipe(
      gulpDebug({
        title: "build-sln",
      })
    ).pipe(throwIfNoFiles(`No solutions found matching masks: ${slnMasks}}`));

  // TODO: find a reliable, quick way to determine if the projects to be compiled
  //       are all dotnet core -- trawling *.csproj is slow and has caused hangups
  //       here, so for now, DNC build must be requested via env DONET_CORE
  return env.resolveFlag("DOTNET_CORE")
    ? buildForNetCore(solutions)
    : buildForNETFramework(solutions);
}

function buildForNetCore(solutions) {
  log.info(gutil.colors.yellow("Building with dotnet core"));
  const configuration = env.resolve("BUILD_CONFIGURATION");
  return promisifyStream(
    solutions
      .pipe(
        dotnetClean({
          configuration
        })
      )
      .pipe(
        dotnetBuild({
          verbosity: "minimal",
          configuration
        })
      )
  );
}

function buildForNETFramework(solutions) {
  log.info(chalk.yellowBright("Building with full .NET framework"));
  return promisifyStream(buildAsStream(solutions));
}

function buildAsStream(solutions) {
  const builder = os.platform() === "win32" ? msbuild : xbuild;
  const config = {
    toolsVersion: env.resolve("BUILD_TOOLSVERSION"),
    targets: env.resolveArray("BUILD_TARGETS"),
    configuration: env.resolve("BUILD_CONFIGURATION"),
    stdout: true,
    verbosity: env.resolve("BUILD_VERBOSITY"),
    errorOnFail: env.resolveFlag("BUILD_FAIL_ON_ERROR"),
    solutionPlatform: env.resolve("BUILD_PLATFORM"),
    // NB: this is the MSBUILD architecture, NOT your desired output architecture
    architecture: env.resolve("BUILD_ARCHITECTURE"),
    nologo: false,
    logCommand: true,
    nodeReuse: env.resolveFlag("BUILD_MSBUILD_NODE_REUSE"),
    maxcpucount: env.resolveNumber("BUILD_MAX_CPU_COUNT")
  };

  if (env.resolveFlag("BUILD_SHOW_INFO")) {
    logConfig(config, {
      toolsVersion: "Tools version",
      targets: "Build targets",
      configuration: "Build configuration",
      stdout: "Log to stdout",
      verbosity: "Build verbosity",
      errorOnFail: "Any error fails the build",
      solutionPlatform: "Build platform",
      architecture: "Build architecture",
      nodeReuse: "Re-use MSBUILD nodes",
      maxcpucount: "Max CPUs to use for build",
    });
  }
  return solutions
    .pipe(gulpDebug({ title: "before msbuild" }))
    .pipe(builder(config))
    .on("end", function() {
      gulpDebug({ title: "solutions pipe ends" });
    })
    .pipe(gulpDebug({ title: "after msbuild" }));
}
<|endoftext|>


<commit_before>'use strict';

var t = require('babel-types');

var _require = require('babel-macros'),
    createMacro = _require.createMacro;

function checkIdxArguments(file, node) {
  var args = node.arguments;
  if (args.length !== 2) {
    throw file.buildCodeFrameError(node, 'The `idx` function takes exactly two arguments.');
  }
  var arrowFunction = args[1];
  if (!t.isArrowFunctionExpression(arrowFunction)) {
    throw file.buildCodeFrameError(arrowFunction, 'The second argument supplied to `idx` must be an arrow function.');
  }
  if (!t.isExpression(arrowFunction.body)) {
    throw file.buildCodeFrameError(arrowFunction.body, 'The body of the arrow function supplied to `idx` must be a single ' + 'expression (without curly braces).');
  }
  if (arrowFunction.params.length !== 1) {
    throw file.buildCodeFrameError(arrowFunction.params[2] || arrowFunction, 'The arrow function supplied to `idx` must take exactly one parameter.');
  }
  var input = arrowFunction.params[0];
  if (!t.isIdentifier(input)) {
    throw file.buildCodeFrameError(arrowFunction.params[0], 'The parameter supplied to `idx` must be an identifier.');
  }
}

function makeCondition(node, state, inside) {
  if (inside) {
    return t.ConditionalExpression(t.BinaryExpression('!=', t.AssignmentExpression('=', state.temp, node), t.NullLiteral()), inside, state.temp);
  } else {
    return node;
  }
}

function makeChain(node, state, inside) {
  if (t.isCallExpression(node)) {
    return makeChain(node.callee, state, makeCondition(t.CallExpression(state.temp, node.arguments), state, inside));
  } else if (t.isMemberExpression(node)) {
    return makeChain(node.object, state, makeCondition(t.MemberExpression(state.temp, node.property, node.computed), state, inside));
  } else if (t.isIdentifier(node)) {
    if (node.name !== state.base.name) {
      throw state.file.buildCodeFrameError(node, 'The parameter of the arrow function supplied to `idx` must match ' + 'the base of the body expression.');
    }
    return makeCondition(state.input, state, inside);
  } else {
    throw state.file.buildCodeFrameError(node, 'The `idx` body can only be composed of properties and methods.');
  }
}

var idx_transform = function idx_transform(path, state) {
  // const args = path.get('arguments');
  var node = path.node;
  checkIdxArguments(state.file, node);
  var temp = path.scope.generateUidIdentifier('ref');
  var replacement = makeChain(node.arguments[1].body, {
    file: state.file,
    input: node.arguments[0],
    base: node.arguments[1].params[0],
    temp: temp
  });
  path.replaceWith(replacement);
  path.scope.push({ id: temp });
};

module.exports = createMacro(function (_ref) {
  var state = _ref.state,
      references = _ref.references;

  references.default.forEach(function (referencePath) {
    if (referencePath.parentPath.type === 'CallExpression') {
      idx_transform(referencePath.parentPath, state);
    } else {
      throw Error('Oh boi');
    }
  });
});<commit_msg>Build...<commit_after>'use strict';

var t = require('babel-types');

var _require = require('babel-plugin-macros'),
    createMacro = _require.createMacro;

function checkIdxArguments(file, node) {
  var args = node.arguments;
  if (args.length !== 2) {
    throw file.buildCodeFrameError(node, 'The `idx` function takes exactly two arguments.');
  }
  var arrowFunction = args[1];
  if (!t.isArrowFunctionExpression(arrowFunction)) {
    throw file.buildCodeFrameError(arrowFunction, 'The second argument supplied to `idx` must be an arrow function.');
  }
  if (!t.isExpression(arrowFunction.body)) {
    throw file.buildCodeFrameError(arrowFunction.body, 'The body of the arrow function supplied to `idx` must be a single ' + 'expression (without curly braces).');
  }
  if (arrowFunction.params.length !== 1) {
    throw file.buildCodeFrameError(arrowFunction.params[2] || arrowFunction, 'The arrow function supplied to `idx` must take exactly one parameter.');
  }
  var input = arrowFunction.params[0];
  if (!t.isIdentifier(input)) {
    throw file.buildCodeFrameError(arrowFunction.params[0], 'The parameter supplied to `idx` must be an identifier.');
  }
}

function makeCondition(node, state, inside) {
  if (inside) {
    return t.ConditionalExpression(t.BinaryExpression('!=', t.AssignmentExpression('=', state.temp, node), t.NullLiteral()), inside, state.temp);
  } else {
    return node;
  }
}

function makeChain(node, state, inside) {
  if (t.isCallExpression(node)) {
    return makeChain(node.callee, state, makeCondition(t.CallExpression(state.temp, node.arguments), state, inside));
  } else if (t.isMemberExpression(node)) {
    return makeChain(node.object, state, makeCondition(t.MemberExpression(state.temp, node.property, node.computed), state, inside));
  } else if (t.isIdentifier(node)) {
    if (node.name !== state.base.name) {
      throw state.file.buildCodeFrameError(node, 'The parameter of the arrow function supplied to `idx` must match ' + 'the base of the body expression.');
    }
    return makeCondition(state.input, state, inside);
  } else {
    throw state.file.buildCodeFrameError(node, 'The `idx` body can only be composed of properties and methods.');
  }
}

var idx_transform = function idx_transform(path, state) {
  // const args = path.get('arguments');
  var node = path.node;
  checkIdxArguments(state.file, node);
  var temp = path.scope.generateUidIdentifier('ref');
  var replacement = makeChain(node.arguments[1].body, {
    file: state.file,
    input: node.arguments[0],
    base: node.arguments[1].params[0],
    temp: temp
  });
  path.replaceWith(replacement);
  path.scope.push({ id: temp });
};

module.exports = createMacro(function (_ref) {
  var state = _ref.state,
      references = _ref.references;

  references.default.forEach(function (referencePath) {
    if (referencePath.parentPath.type === 'CallExpression') {
      idx_transform(referencePath.parentPath, state);
    } else {
      throw Error('idx.macro can only be used a function, and can not be passed around as an argument.');
    }
  });
});<|endoftext|>