<commit_before>var sq = getUrlParameter('sq');
try {
    /*** get rid of ?sq= parameter ASAP ***/
    if (sq && sq !== undefined) {
        var re = new RegExp("(\\?|&)sq=" + escapeRegExp(encodeURIComponent(sq)));
        var newurl = location.href.replace(re, '');
        if (~newurl.indexOf('&') && !~newurl.indexOf('?')) {
            newurl = newurl.replace('&', '?');
        }
        window.history.replaceState({}, '', newurl);
    }

} catch (err) {
    console.log("Couldn't replaceState! Oh well, not a big deal.");
    console.err(err);
}
/****** DOM STUFF *********/

// adjusts altitude of content and navbar, in case header size changes.
function nav_altitude() {

    var h = $('#menudocumentation').height();
    if ($("#menudocumentation").css("position") != "fixed") {
        $('#resizable').css("top", 0);
    } else {
        $('#resizable').css("top", h);
    }
    $('.content').css("padding-top", h + 40);

    if (!$('#resizable').is(":visible")) {
        $('.content').css('padding-left', 15);
    } else {
        $('.content').css('padding-left', parseInt($('#resizable').css('width')) + 100);
    }

}

$(document).ready(function() {
    $("#searchbox").val(sq);
    // Contains = case insensitive 'contains'
    $.expr[":"].Contains = jQuery.expr.createPseudo(function(arg) {
        return function(elem) {
            return jQuery(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
        };
    });

/*** highlight previous search query on page ****/
    if (sq !== undefined) {
         var x = $('body').highlight(sq);
        var el = $("span.search-highlight");
        if (el.length > 0) {
            var top = el.first().offset().top - (window.innerHeight / 2);
            window.scrollTo(0, top);

        }
        $(document).bind('keydown', 'esc', function(){$('body').removeHighlight()});
    }

    $("#searchbox").keyup(function(e) {
        if (e.which >= 37 && e.which <= 40) // ignore if it's an arrow key
            return;
        getdata();
    });
    $("#searchbox").click(function() {
        getdata();
    });
    var $content = $('.content');
    var is_mobile = screen.width <= 719;

    if (!is_mobile) {
        $("#left").perfectScrollbar();
    } else {
        $("#left").detach().appendTo('#top');
        $("#resizable").remove();
    }

    nav_altitude();
    $(window).resize(nav_altitude);
    // closing and opening of hamburger
    $("#menu-trigger").click(function() {
        $("#top")[this.opened ? "removeClass" : "addClass"]("menu-open");
        $(this)[this.opened ? "removeClass" : "addClass"]("trigger-open");
        this.opened = !this.opened;
    });
    $(".content").click(function() {
        $("#top").removeClass("menu-open");
        $("#menu-trigger").removeClass("trigger-open");
        $("#menu-trigger")[0].opened = false;
    });
    $("#top a").click(function() {
        $("#top").removeClass("menu-open");
        $("#menu-trigger").removeClass("trigger-open").get(0).opened = false;

    });

    var cookieName = 'navwidth';
    var width = parseInt($.cookie(cookieName)) || 300;

    var preventNextClick = false; // so resize doesn't trigger click

    function adjust_content_padding(w) {
        $content.css('padding-left', w + 100 + 'px');
    }

    $('#resizable').css('width', width + 'px');
    adjust_content_padding(width);

    $('#resizable').resizable({

        handles: 'e',
        maxWidth: 500,
        minWidth: 5,
        resize: function(event, ui) {

            adjust_content_padding(ui.size.width);
            $.cookie(cookieName, ui.size.width);
            width = ui.size.width;

        },
        stop: function(event, ui) {
            if ($(".ui-resizable-e").is(":hover"))
                preventNextClick = true;
        }
    });
    $(".ui-resizable-e").click(function() {
        if (preventNextClick) {
            preventNextClick = false;
            return;
        }
        var $p = $(this).parent();
        if ($p.width() < width) {
            $p.width(width);
        } else if ($p.width() == 5) {
            width = 300;
            $p.width(300);
        } else {
            $p.width(5);
        }
        adjust_content_padding($p.width());
    })
});

/******* SEARCHING *******/

// perform a search.
// ctx: context of current page, e.g. "Build Apps"
function getdata() {
    ctx = $('html').data('context');
    var domain = "";
    var invoke_url = "https://x6khds7mia.execute-api.us-west-2.amazonaws.com/prod";
    if (ctx == "Legato Documentation")
        ctx = "";
    var keyword = $('#searchbox').val();
    var ac = $("#searchbox").autocomplete({
        delay: 10,
        autoFocus: true,
        source: function(request, response) {
            $.ajax({
                url: invoke_url,
                dataType: "json",
                data: new function() {
                    this.q = request.term;
                    this.size = 25;
                    if (ctx != "")
                        this.fq = "context:'" + ctx + "'"; //only include "context:" filter if ctx isnt empty
                    this.sort = "_score desc";
                    this.return = "category,title,context,id";
                },
                change: function(e, ui) {
                    console.log(e.target.value);
                },

                success: function(data) {
                    var hits = data.hits.hit;
                    var results = []
                    if (hits.length == 0) {
                        results.push({
                            value: "javascript:void(0);",
                            label: "No results found :("
                        });
                    } else
                        for (i = 0; i < hits.length; i++) {
                            var result = new Object();

                            result.value = addParameter(hits[i].id, 'sq', request.term, false);
                            result.cat = hits[i].fields.category;
                            if (result.cat === undefined)
                                result.cat = "Root"

                            result.label = "" + result.cat + "
" + hits[i].fields.title + "";

                            result.title = hits[i].fields.title;
                            results.push(result);
                        }
                    response(results);
                }
            });

        },

        minLength: 1,
        focus: function(event, ui) {
            event.preventDefault(); // so the textbox's value doesn't get replaced.
        },
        select: function(event, ui) {
            event.preventDefault();
            if (ui.item) {
                window.location = ui.item.value;
            }
        },
        open: function() {
            $(this).removeClass("ui-corner-all").addClass("ui-corner-top");

        },
        close: function() {
            $(this).removeClass("ui-corner-top").addClass("ui-corner-all");
        }
    });

    ac.data("ui-autocomplete")._renderItem = function(ul, item) {
        var anchor = $("").attr("href", item.value).html(item.label);
        var li = $("
");
        anchor.appendTo(li)
        return li.appendTo(ul);
    };

    ac.data("ui-autocomplete")._renderMenu = function(ul, items) {
        var that = this;
        ul.attr('id', "search_result");
        ul.attr('class', "ui-autocomplete ui-front ui-menu ui-widget ui-widget-content");
        //ul.attr('data-resultcount',items.length + " results.");
        $.each(items, function(index, item) {
            that._renderItemData(ul, item);
        });
    };
    $("#searchbox").autocomplete("search", keyword);

}
/***** NAVIGATION TREE ******/

function setupTree(treeURL) {
    if (treeURL != "") {

        $(document).ready(function() {
            String.prototype.endsWith = function(suffix) {
                return this.indexOf(suffix, this.length - suffix.length) !== -1;
            };
            $.getJSON(
                treeURL,
                function(data) {
                    $tree = $('#tree1');
                    $tree.tree({
                        data: data.children ? data.children : data,
                        saveState: false,
                        useContextMenu: false,
                        slide: false,
                        closedIcon: "+",
                        openedIcon: "-",
                        keyboardSupport: false,

                        onCreateLi: function(node, $li) {
                            var a = $li.find('span')[0];
                            a.outerHTML = '' + a.outerHTML + '';
                        },

                    });

                    $tree.tree('selectNode', null); //unselect nodes

                    $("#left").perfectScrollbar('update');

                    $tree.bind(
                        'tree.close',
                        function(e) {
                            $("#left").perfectScrollbar('update');
                        }
                    );
                    $tree.bind(
                        'tree.open',
                        function(e) {
                            $("#left").perfectScrollbar('update');
                        }
                    );

                    // we want to get
                    var path = window.location.pathname; // path = /docs/filename.html
                    var page = path.split("/").pop(); // page = filename.html
                    var anchor = window.location.hash.substr(1); // anchor = section
                    if (anchor)
                        page += "#" + anchor; // page = filename.html#section

                    // find node of current page and select it
                    $tree.tree('getTree').iterate(
                        function(node, level) {
                            if (node.href === page) {
                                $tree.tree('openNode', node);
                                $tree.tree('selectNode', node);
                                return false;
                            }
                            return true;
                        }
                    );
                    $('#tree1').tree('setOption', 'slide', true); // now that only the user will be opening nodes, we can turn the animation back on.
                    // needed when clicking an anchor, and thus staying on the same page
                    $tree.bind(
                        'tree.click',
                        function(event) {
                            $tree.tree('setOption', 'selectable', true);
                            $tree.tree('selectNode', event.node);
                            $tree.tree('setOption', 'selectable', false);
                        }
                    );

                    $('#tree1').tree('setOption', 'selectable', false);

                }
            );

        });
    }
}

/*** utils ***/

// http://stackoverflow.com/a/6954277/765210
function addParameter(url, parameterName, parameterValue, atStart /*Add param before others*/ ) {
    replaceDuplicates = true;
    if (url.indexOf('#') > 0) {
        var cl = url.indexOf('#');
        urlhash = url.substring(url.indexOf('#'), url.length);
    } else {
        urlhash = '';
        cl = url.length;
    }
    sourceUrl = url.substring(0, cl);

    var urlParts = sourceUrl.split("?");
    var newQueryString = "";

    if (urlParts.length > 1) {
        var parameters = urlParts[1].split("&");
        for (var i = 0;
            (i < parameters.length); i++) {
            var parameterParts = parameters[i].split("=");
            if (!(replaceDuplicates && parameterParts[0] == parameterName)) {
                if (newQueryString == "")
                    newQueryString = "?";
                else
                    newQueryString += "&";
                newQueryString += parameterParts[0] + "=" + (parameterParts[1] ? parameterParts[1] : '');
            }
        }
    }
    if (newQueryString == "")
        newQueryString = "?";

    if (atStart) {
        newQueryString = '?' + parameterName + "=" + parameterValue + (newQueryString.length > 1 ? '&' + newQueryString.substring(1) : '');
    } else {
        if (newQueryString !== "" && newQueryString != '?')
            newQueryString += "&";
        newQueryString += parameterName + "=" + (parameterValue ? parameterValue : '');
    }
    return urlParts[0] + newQueryString + urlhash;
};
// http://stackoverflow.com/a/21903119/765210
function getUrlParameter(sParam) {
    var sPageURL = decodeURIComponent(window.location.search.substring(1)),
        sURLVariables = sPageURL.split('&'),
        sParameterName,
        i;

    for (i = 0; i < sURLVariables.length; i++) {
        sParameterName = sURLVariables[i].split('=');

        if (sParameterName[0] === sParam) {
            return sParameterName[1] === undefined ? true : sParameterName[1];
        }
    }
}

function escapeRegExp(str) {
    return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}

/*

highlight v5

Highlights arbitrary terms.

<http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html>

MIT license.

Johann Burkard
<http://johannburkard.de>
<mailto:jb@eaio.com>

*/

jQuery.fn.highlight = function(pat) {
 function innerHighlight(node, pat) {
  var skip = 0;
  if (node.nodeType == 3) {
   var pos = node.data.toUpperCase().indexOf(pat);
   pos -= (node.data.substr(0, pos).toUpperCase().length - node.data.substr(0, pos).length);
   if (pos >= 0) {
    var spannode = document.createElement('span');
    spannode.className = 'search-highlight';
    var middlebit = node.splitText(pos);
    var endbit = middlebit.splitText(pat.length);
    var middleclone = middlebit.cloneNode(true);
    spannode.appendChild(middleclone);
    middlebit.parentNode.replaceChild(spannode, middlebit);
    skip = 1;
   }
  }
  else if (node.nodeType == 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) {
   for (var i = 0; i < node.childNodes.length; ++i) {
    i += innerHighlight(node.childNodes[i], pat);
   }
  }
  return skip;
 }
 return this.length && pat && pat.length ? this.each(function() {
  innerHighlight(this, pat.toUpperCase());
 }) : this;
};

jQuery.fn.removeHighlight = function() {
 return this.find("span.search-highlight").each(function() {
  this.parentNode.firstChild.nodeName;
  with (this.parentNode) {
   replaceChild(this.firstChild, this);
   normalize();
  }
 }).end();
};<commit_msg>strip out non-alphanumeric characters (except _) for highlighting, since cloudsearch ignores them anyway<commit_after>var sq = getUrlParameter('sq');
try {
    /*** get rid of ?sq= parameter ASAP ***/
    if (sq && sq !== undefined) {
        var re = new RegExp("(\\?|&)sq=" + escapeRegExp(encodeURIComponent(sq)));
        var newurl = location.href.replace(re, '');
        if (~newurl.indexOf('&') && !~newurl.indexOf('?')) {
            newurl = newurl.replace('&', '?');
        }
        window.history.replaceState({}, '', newurl);
    }

} catch (err) {
    console.log("Couldn't replaceState! Oh well, not a big deal.");
    console.err(err);
}
/****** DOM STUFF *********/

// adjusts altitude of content and navbar, in case header size changes.
function nav_altitude() {

    var h = $('#menudocumentation').height();
    if ($("#menudocumentation").css("position") != "fixed") {
        $('#resizable').css("top", 0);
    } else {
        $('#resizable').css("top", h);
    }
    $('.content').css("padding-top", h + 40);

    if (!$('#resizable').is(":visible")) {
        $('.content').css('padding-left', 15);
    } else {
        $('.content').css('padding-left', parseInt($('#resizable').css('width')) + 100);
    }

}

$(document).ready(function() {
    $("#searchbox").val(sq);
    // Contains = case insensitive 'contains'
    $.expr[":"].Contains = jQuery.expr.createPseudo(function(arg) {
        return function(elem) {
            return jQuery(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
        };
    });

/*** highlight previous search query on page ****/
    if (sq !== undefined) {
         var x = $('body').highlight(sq.replace(/[\W]/g, ''));
        var el = $("span.search-highlight");
        if (el.length > 0) {
            var top = el.first().offset().top - (window.innerHeight / 2);
            window.scrollTo(0, top);

        }
        $(document).bind('keydown', 'esc', function(){$('body').removeHighlight()});
    }

    $("#searchbox").keyup(function(e) {
        if (e.which >= 37 && e.which <= 40) // ignore if it's an arrow key
            return;
        getdata();
    });
    $("#searchbox").click(function() {
        getdata();
    });
    var $content = $('.content');
    var is_mobile = screen.width <= 719;

    if (!is_mobile) {
        $("#left").perfectScrollbar();
    } else {
        $("#left").detach().appendTo('#top');
        $("#resizable").remove();
    }

    nav_altitude();
    $(window).resize(nav_altitude);
    // closing and opening of hamburger
    $("#menu-trigger").click(function() {
        $("#top")[this.opened ? "removeClass" : "addClass"]("menu-open");
        $(this)[this.opened ? "removeClass" : "addClass"]("trigger-open");
        this.opened = !this.opened;
    });
    $(".content").click(function() {
        $("#top").removeClass("menu-open");
        $("#menu-trigger").removeClass("trigger-open");
        $("#menu-trigger")[0].opened = false;
    });
    $("#top a").click(function() {
        $("#top").removeClass("menu-open");
        $("#menu-trigger").removeClass("trigger-open").get(0).opened = false;

    });

    var cookieName = 'navwidth';
    var width = parseInt($.cookie(cookieName)) || 300;

    var preventNextClick = false; // so resize doesn't trigger click

    function adjust_content_padding(w) {
        $content.css('padding-left', w + 100 + 'px');
    }

    $('#resizable').css('width', width + 'px');
    adjust_content_padding(width);

    $('#resizable').resizable({

        handles: 'e',
        maxWidth: 500,
        minWidth: 5,
        resize: function(event, ui) {

            adjust_content_padding(ui.size.width);
            $.cookie(cookieName, ui.size.width);
            width = ui.size.width;

        },
        stop: function(event, ui) {
            if ($(".ui-resizable-e").is(":hover"))
                preventNextClick = true;
        }
    });
    $(".ui-resizable-e").click(function() {
        if (preventNextClick) {
            preventNextClick = false;
            return;
        }
        var $p = $(this).parent();
        if ($p.width() < width) {
            $p.width(width);
        } else if ($p.width() == 5) {
            width = 300;
            $p.width(300);
        } else {
            $p.width(5);
        }
        adjust_content_padding($p.width());
    })
});

/******* SEARCHING *******/

// perform a search.
// ctx: context of current page, e.g. "Build Apps"
function getdata() {
    ctx = $('html').data('context');
    var domain = "";
    var invoke_url = "https://x6khds7mia.execute-api.us-west-2.amazonaws.com/prod";
    if (ctx == "Legato Documentation")
        ctx = "";
    var keyword = $('#searchbox').val();
    var ac = $("#searchbox").autocomplete({
        delay: 10,
        autoFocus: true,
        source: function(request, response) {
            $.ajax({
                url: invoke_url,
                dataType: "json",
                data: new function() {
                    this.q = request.term;
                    this.size = 25;
                    if (ctx != "")
                        this.fq = "context:'" + ctx + "'"; //only include "context:" filter if ctx isnt empty
                    this.sort = "_score desc";
                    this.return = "category,title,context,id";
                },
                change: function(e, ui) {
                    console.log(e.target.value);
                },

                success: function(data) {
                    var hits = data.hits.hit;
                    var results = []
                    if (hits.length == 0) {
                        results.push({
                            value: "javascript:void(0);",
                            label: "No results found :("
                        });
                    } else
                        for (i = 0; i < hits.length; i++) {
                            var result = new Object();

                            result.value = addParameter(hits[i].id, 'sq', request.term, false);
                            result.cat = hits[i].fields.category;
                            if (result.cat === undefined)
                                result.cat = "Root"

                            result.label = "" + result.cat + "
" + hits[i].fields.title + "";

                            result.title = hits[i].fields.title;
                            results.push(result);
                        }
                    response(results);
                }
            });

        },

        minLength: 1,
        focus: function(event, ui) {
            event.preventDefault(); // so the textbox's value doesn't get replaced.
        },
        select: function(event, ui) {
            event.preventDefault();
            if (ui.item) {
                window.location = ui.item.value;
            }
        },
        open: function() {
            $(this).removeClass("ui-corner-all").addClass("ui-corner-top");

        },
        close: function() {
            $(this).removeClass("ui-corner-top").addClass("ui-corner-all");
        }
    });

    ac.data("ui-autocomplete")._renderItem = function(ul, item) {
        var anchor = $("").attr("href", item.value).html(item.label);
        var li = $("
");
        anchor.appendTo(li)
        return li.appendTo(ul);
    };

    ac.data("ui-autocomplete")._renderMenu = function(ul, items) {
        var that = this;
        ul.attr('id', "search_result");
        ul.attr('class', "ui-autocomplete ui-front ui-menu ui-widget ui-widget-content");
        //ul.attr('data-resultcount',items.length + " results.");
        $.each(items, function(index, item) {
            that._renderItemData(ul, item);
        });
    };
    $("#searchbox").autocomplete("search", keyword);

}
/***** NAVIGATION TREE ******/

function setupTree(treeURL) {
    if (treeURL != "") {

        $(document).ready(function() {
            String.prototype.endsWith = function(suffix) {
                return this.indexOf(suffix, this.length - suffix.length) !== -1;
            };
            $.getJSON(
                treeURL,
                function(data) {
                    $tree = $('#tree1');
                    $tree.tree({
                        data: data.children ? data.children : data,
                        saveState: false,
                        useContextMenu: false,
                        slide: false,
                        closedIcon: "+",
                        openedIcon: "-",
                        keyboardSupport: false,

                        onCreateLi: function(node, $li) {
                            var a = $li.find('span')[0];
                            a.outerHTML = '' + a.outerHTML + '';
                        },

                    });

                    $tree.tree('selectNode', null); //unselect nodes

                    $("#left").perfectScrollbar('update');

                    $tree.bind(
                        'tree.close',
                        function(e) {
                            $("#left").perfectScrollbar('update');
                        }
                    );
                    $tree.bind(
                        'tree.open',
                        function(e) {
                            $("#left").perfectScrollbar('update');
                        }
                    );

                    // we want to get
                    var path = window.location.pathname; // path = /docs/filename.html
                    var page = path.split("/").pop(); // page = filename.html
                    var anchor = window.location.hash.substr(1); // anchor = section
                    if (anchor)
                        page += "#" + anchor; // page = filename.html#section

                    // find node of current page and select it
                    $tree.tree('getTree').iterate(
                        function(node, level) {
                            if (node.href === page) {
                                $tree.tree('openNode', node);
                                $tree.tree('selectNode', node);
                                return false;
                            }
                            return true;
                        }
                    );
                    $('#tree1').tree('setOption', 'slide', true); // now that only the user will be opening nodes, we can turn the animation back on.
                    // needed when clicking an anchor, and thus staying on the same page
                    $tree.bind(
                        'tree.click',
                        function(event) {
                            $tree.tree('setOption', 'selectable', true);
                            $tree.tree('selectNode', event.node);
                            $tree.tree('setOption', 'selectable', false);
                        }
                    );

                    $('#tree1').tree('setOption', 'selectable', false);

                }
            );

        });
    }
}

/*** utils ***/

// http://stackoverflow.com/a/6954277/765210
function addParameter(url, parameterName, parameterValue, atStart /*Add param before others*/ ) {
    replaceDuplicates = true;
    if (url.indexOf('#') > 0) {
        var cl = url.indexOf('#');
        urlhash = url.substring(url.indexOf('#'), url.length);
    } else {
        urlhash = '';
        cl = url.length;
    }
    sourceUrl = url.substring(0, cl);

    var urlParts = sourceUrl.split("?");
    var newQueryString = "";

    if (urlParts.length > 1) {
        var parameters = urlParts[1].split("&");
        for (var i = 0;
            (i < parameters.length); i++) {
            var parameterParts = parameters[i].split("=");
            if (!(replaceDuplicates && parameterParts[0] == parameterName)) {
                if (newQueryString == "")
                    newQueryString = "?";
                else
                    newQueryString += "&";
                newQueryString += parameterParts[0] + "=" + (parameterParts[1] ? parameterParts[1] : '');
            }
        }
    }
    if (newQueryString == "")
        newQueryString = "?";

    if (atStart) {
        newQueryString = '?' + parameterName + "=" + parameterValue + (newQueryString.length > 1 ? '&' + newQueryString.substring(1) : '');
    } else {
        if (newQueryString !== "" && newQueryString != '?')
            newQueryString += "&";
        newQueryString += parameterName + "=" + (parameterValue ? parameterValue : '');
    }
    return urlParts[0] + newQueryString + urlhash;
};
// http://stackoverflow.com/a/21903119/765210
function getUrlParameter(sParam) {
    var sPageURL = decodeURIComponent(window.location.search.substring(1)),
        sURLVariables = sPageURL.split('&'),
        sParameterName,
        i;

    for (i = 0; i < sURLVariables.length; i++) {
        sParameterName = sURLVariables[i].split('=');

        if (sParameterName[0] === sParam) {
            return sParameterName[1] === undefined ? true : sParameterName[1];
        }
    }
}

function escapeRegExp(str) {
    return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}

/*

highlight v5

Highlights arbitrary terms.

<http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html>

MIT license.

Johann Burkard
<http://johannburkard.de>
<mailto:jb@eaio.com>

*/

jQuery.fn.highlight = function(pat) {
 function innerHighlight(node, pat) {
  var skip = 0;
  if (node.nodeType == 3) {
   var pos = node.data.toUpperCase().indexOf(pat);
   pos -= (node.data.substr(0, pos).toUpperCase().length - node.data.substr(0, pos).length);
   if (pos >= 0) {
    var spannode = document.createElement('span');
    spannode.className = 'search-highlight';
    var middlebit = node.splitText(pos);
    var endbit = middlebit.splitText(pat.length);
    var middleclone = middlebit.cloneNode(true);
    spannode.appendChild(middleclone);
    middlebit.parentNode.replaceChild(spannode, middlebit);
    skip = 1;
   }
  }
  else if (node.nodeType == 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) {
   for (var i = 0; i < node.childNodes.length; ++i) {
    i += innerHighlight(node.childNodes[i], pat);
   }
  }
  return skip;
 }
 return this.length && pat && pat.length ? this.each(function() {
  innerHighlight(this, pat.toUpperCase());
 }) : this;
};

jQuery.fn.removeHighlight = function() {
 return this.find("span.search-highlight").each(function() {
  this.parentNode.firstChild.nodeName;
  with (this.parentNode) {
   replaceChild(this.firstChild, this);
   normalize();
  }
 }).end();
};<|endoftext|>


<commit_before>// JSFastPack removes comments and extraneous whitespace
// from Javascript source files. Particular attention was given
// towards tuning code making it as fast as possible. In tests
// it packed roughly 5 MB per second, on average
function JSFastPack(inString)
{
	var i			= 0;
	var result		= '';
	var ch;
	var code;
	var lastType;
	var lastChar;
	var delim;

	// Without some form of context tracking, detecting
	// regular expressions, character by character,
	// is really cumbersome, so before doing anything
	// else, temporarily encode them as base64 strings
	// bounded with '<re_' start and '>' end delimiters.
	// In this form, they can be easily detected during
	// packing and restored to their original value.
	// The reason this extra step is necessary is that
	// the packing algorithm is specifically designed
	// to remove as much whitespace as possible which,
	// if applied within regular expressions, could
	// potentially change what they do.

	var encodeMatch	= function(inMatch)
	{
		var prefix;
		var exp;
		var index;

		if (inMatch.charAt(0) == '=')
		{
			// regex assigned to variable
			// ex: re	= /[a-z]+/g;
			index	= inMatch.indexOf('/');
			prefix	= inMatch.slice(0, index);
			exp		= inMatch.substring(index);
		}
		else
		{
			// regex defined in function
			// ex: str.match(/[a-z]+/g);
			index	= inMatch.indexOf('(');
			prefix	= inMatch.slice(0, index + 1);
			exp		= inMatch.substring(index + 1);
		}

		return prefix + '<re_' + Base64EncodeString(exp) + '>';
	}

	var re			= /(=\s+|\.(match|test|exec|search|replace|split)\()(\/.+\/)([gimy]+)?/g;
	var src			= inString.replace(re, encodeMatch);
	var len			= src.length;

	while (i < len)
	{
		// fetching just char code is about 4 percent faster,
		// on average, than fetching both char code
		// and char together.
		code		= src.charCodeAt(i);

		if (code == 47)		// '/'
		{
			// might be a comment start, test next char
			if (src.charCodeAt(i + 1) == 47)	// '/'
			{
				// single line comment start, skip it
				i += 2;
				while (i < len)
				{
					ch			= src.charAt(i++);

					if ((ch == '\r') ||
						(ch == '\n'))
					{
						// comment end found, break out of loop
						break;
					}
				}
			}
			else if (src.charCodeAt(i + 1) == 42)	// '*'
			{
				// multi line comment start, skip it
				i += 2;
				while (i < len)
				{
					if ((src.charAt(i++) == '*') &&
						(src.charAt(i) == '/'))
					{
						// comment end found, break out of loop
						i++;
						break;
					}
				}
			}
			else
			{
				// not a comment, add char to result
				result		+= src.charAt(i++);
				lastType	= 'c';
			}
		}
		// detecting and decoding previously encoded base64 regexes,
		// on-the-fly is roughly 9 percent faster than decoding
		// them with a dedicated function after packing
		else if (code == 60)	// '<'
		{
			// might be encoded regular expression
			if ((i + 4) < len)
			{
				// test for encoding prefix
				if ((src.charAt(i + 1) == 'r') &&
					(src.charAt(i + 2) == 'e') &&
					(src.charAt(i + 3) == '_'))
				{
					// encoded regular expression, decode it
					var re		= '';

					// skip '<re_' encoding start delim
					i += 4;

					// loop until encoding end delim (>) is found
					while (i < len)
					{
						ch	= src.charAt(i++);
						if (ch == '>')
							break;
						else
							re	+= ch;
					}

					// decode expression and add to result
					result		+= Base64DecodeString(re);
					lastType	= 'c';
				}
				else
				{
					// not the '<re_' encoding prefix, just add char
					result		+= src.charAt(i++);
					lastType	= 'c';
				}
			}
			else
			{
				// not enough room for encoded prefix. just add char
				result		+= src.charAt(i++);
				lastType	= 'c';
			}
		}
		else if ((code == 34) ||	// double quote
				 (code == 39))		// single quote
		{
			// string start, pack it
			delim		= src.charAt(i++);
			result		+= delim;

			while (i < len)
			{
				ch		= src.charAt(i++);

				if ((ch == '\\') &&
					(lastChar == '\\'))
				{
					// void char value (prevents erroneous escapes)
					ch	= undefined;
				}
				else if ((ch == delim) &&
					(lastChar != '\\'))
				{
					// found string end, break out of loop
					result		+= delim;
					lastType	= 'c';
					break;
				}
				else
					result	+= ch;

				lastChar = ch;
			}
			lastType	= 'c';
		}
		// testing code ranges is about 4 percent
		// faster, on average, than using indexOf
		// on a string containing code chars
		else if (((code > 34) && (code < 39))	||	// # $ % &
			((code > 39) && (code < 48)) 		||	// ( ) * + , - . /
			((code > 57) && (code < 65)) 		||	// : ; < = > ? @
			((code > 90) && (code < 97)) 		||	// [ \ ] ^ _ `
			((code > 122) && (code < 127)) 		||	// { \ } ~
			(code == 33))							// !
		{
			// code char, add it
			result		+= src.charAt(i++);
			lastType	= 'c';
		}
		// testing whitespace code range is about 7
		// percent faster, on average, than testing
		// individual whitespace chars
		else if (((code > 8) &&
				 (code < 14)) ||
				 (code == 32))
		{
			// white char, skip it
			i++;
			while (i < len)
			{
				code	= src.charCodeAt(i);
				if (((code > 8) &&
					(code < 14)) ||
					(code == 32))
					i++;
				else
					 break;
			}

			if (lastType != 'c')
				lastType	= 'w';
		}
		else
		{
			if (lastType == 'w')
				result		+= ' ';

			result		+= src.charAt(i++);
			lastType	= 'n';
		}
	}

	return result;
}

function Base64EncodeString(inString)
{
	var str			= inString;
	var remainder	= str.length % 3;
	var tripleEnd	= Math.floor(str.length / 3) * 3;
	var result		= "";
	var pad			= '=';
	var i			= 0;

	// process complete triples
	while (i < tripleEnd)
	{
		result	+= String.fromCharCode(Base64EncodeChar((str.charCodeAt(i) >> 2) & 0x3F));
		result	+= String.fromCharCode(Base64EncodeChar((str.charCodeAt(i) << 4) & 0x30 | (str.charCodeAt(++i) >> 4) & 0x0F));
		result	+= String.fromCharCode(Base64EncodeChar((str.charCodeAt(i) << 2) & 0x3C | (str.charCodeAt(++i) >> 6) & 0x03));
		result	+= String.fromCharCode(Base64EncodeChar( str.charCodeAt(i) & 0x3F));
		i++;
	}

	// process triple fragments
	if (remainder == 2)
	{
		// add one pad char
		result	+= String.fromCharCode(Base64EncodeChar((str.charCodeAt(i) >> 2) & 0x3F));
		result	+= String.fromCharCode(Base64EncodeChar((str.charCodeAt(i) << 4) & 0x30 | (str.charCodeAt(++i) >> 4) & 0x0F));
		result	+= String.fromCharCode(Base64EncodeChar((str.charCodeAt(i) << 2) & 0x3C));
		result	+= pad;
	}
	else if (remainder == 1)
	{
		// add two pad chars
		result	+= String.fromCharCode(Base64EncodeChar((str.charCodeAt(i) >> 2) & 0x3F));
		result	+= String.fromCharCode(Base64EncodeChar((str.charCodeAt(i) << 4) & 0x30));
		result	+= pad;
		result	+= pad;
	}

	return result;
}

function Base64EncodeChar(inChar)
{
	if (inChar < 26)			// A-Z	= 0 - 25
		return inChar + 65;
	else if (inChar < 52)		// a-z	= 26 - 51
		return inChar + 71;
	else if (inChar < 62) 		// 0-9	= 52 - 61
		return inChar - 4;
	else if (inChar == 62)		// '+'
		return 43;
	else
		return 47;				// '/'
}

function Base64DecodeString(inString)
{
	var str			= inString;
	var tlen		= str.length;
	var pad1		= '=';
	var pad2		= ' ';
	var result		= "";
	var i			= 0;
	var code1, code2;

	while (i < tlen)
	{
		// build the first char
		code1	= (Base64DecodeChar(str.charCodeAt(i))		<< 2) & 0xFC;
		code2	= (Base64DecodeChar(str.charCodeAt(++i))	>> 4) & 0x03;
		result 	+=  String.fromCharCode(code1 | code2);
		if ((str.charAt(i + 1) == pad1) || (str.charAt(i + 1) == pad2)) break;

		// build the second char
		code1	= (Base64DecodeChar(str.charCodeAt(i))		<< 4) & 0xF0;
		code2	= (Base64DecodeChar(str.charCodeAt(++i))	>> 2) & 0x0F;
		result 	+= String.fromCharCode(code1 | code2);
		if ((str.charAt(i + 1) == pad1) || (str.charAt(i + 1) == pad2)) break;

		// build the third char
		code1	= (Base64DecodeChar(str.charCodeAt(i))		<< 6) & 0xC0;
		code2	=  Base64DecodeChar(str.charCodeAt(++i)) 		  & 0x3F;
		result 	+= String.fromCharCode(code1 | code2);

		i++;
	}

	return result;
}

function Base64DecodeChar(inChar)
{
	if (inChar == 43)			// '+'
		return 62;
	else if (inChar == 47)		// '/'
		return 63;
	else if (inChar < 58) 		// 0-9	= 52 - 61
		return inChar + 4;
	else if (inChar < 91)		// A-Z	= 0 - 25
		return inChar - 65;
	else
		return inChar - 71;		// a-z	= 26 - 51
}
<commit_msg>Update JSFastPack.js<commit_after>// JSFastPack removes comments and extraneous whitespace
// from Javascript source files. Particular attention was given
// towards tuning code making it as fast as possible. In tests
// it packing rates of between 5 MB and 15 MB per second.
function JSFastPack(inString)
{
	var i			= 0;
	var result		= '';
	var ch;
	var code;
	var lastType;
	var lastChar;
	var delim;

	// Without some form of context tracking, detecting
	// regular expressions, character by character,
	// is really cumbersome, so before doing anything
	// else, temporarily encode them as base64 strings
	// bounded with '<re_' start and '>' end delimiters.
	// In this form, they can be easily detected during
	// packing and restored to their original value.
	// The reason this extra step is necessary is that
	// the packing algorithm is specifically designed
	// to remove as much whitespace as possible which,
	// if applied within regular expressions, could
	// potentially change what they do.

	var encodeMatch	= function(inMatch)
	{
		var prefix;
		var exp;
		var index;

		if (inMatch.charAt(0) == '=')
		{
			// regex assigned to variable
			// ex: re	= /[a-z]+/g;
			index	= inMatch.indexOf('/');
			prefix	= inMatch.slice(0, index);
			exp		= inMatch.substring(index);
		}
		else
		{
			// regex defined in function
			// ex: str.match(/[a-z]+/g);
			index	= inMatch.indexOf('(');
			prefix	= inMatch.slice(0, index + 1);
			exp		= inMatch.substring(index + 1);
		}

		return prefix + '<re_' + Base64EncodeString(exp) + '>';
	}

	var re			= /(=\s+|\.(match|test|exec|search|replace|split)\()(\/.+\/)([gimy]+)?/g;
	var src			= inString.replace(re, encodeMatch);
	var len			= src.length;

	while (i < len)
	{
		// fetching just char code is about 4 percent faster,
		// on average, than fetching both char code
		// and char together.
		code		= src.charCodeAt(i);

		if (code == 47)		// '/'
		{
			// might be a comment start, test next char
			if (src.charCodeAt(i + 1) == 47)	// '/'
			{
				// single line comment start, skip it
				i += 2;
				while (i < len)
				{
					ch			= src.charAt(i++);

					if ((ch == '\r') ||
						(ch == '\n'))
					{
						// comment end found, break out of loop
						break;
					}
				}
			}
			else if (src.charCodeAt(i + 1) == 42)	// '*'
			{
				// multi line comment start, skip it
				i += 2;
				while (i < len)
				{
					if ((src.charAt(i++) == '*') &&
						(src.charAt(i) == '/'))
					{
						// comment end found, break out of loop
						i++;
						break;
					}
				}
			}
			else
			{
				// not a comment, add char to result
				result		+= src.charAt(i++);
				lastType	= 'c';
			}
		}
		// detecting and decoding previously encoded base64 regexes,
		// on-the-fly is roughly 9 percent faster than decoding
		// them with a dedicated function after packing
		else if (code == 60)	// '<'
		{
			// might be encoded regular expression
			if ((i + 4) < len)
			{
				// test for encoding prefix
				if ((src.charAt(i + 1) == 'r') &&
					(src.charAt(i + 2) == 'e') &&
					(src.charAt(i + 3) == '_'))
				{
					// encoded regular expression, decode it
					var re		= '';

					// skip '<re_' encoding start delim
					i += 4;

					// loop until encoding end delim (>) is found
					while (i < len)
					{
						ch	= src.charAt(i++);
						if (ch == '>')
							break;
						else
							re	+= ch;
					}

					// decode expression and add to result
					result		+= Base64DecodeString(re);
					lastType	= 'c';
				}
				else
				{
					// not the '<re_' encoding prefix, just add char
					result		+= src.charAt(i++);
					lastType	= 'c';
				}
			}
			else
			{
				// not enough room for encoded prefix. just add char
				result		+= src.charAt(i++);
				lastType	= 'c';
			}
		}
		else if ((code == 34) ||	// double quote
				 (code == 39))		// single quote
		{
			// string start, pack it
			delim		= src.charAt(i++);
			result		+= delim;

			while (i < len)
			{
				ch		= src.charAt(i++);

				if ((ch == '\\') &&
					(lastChar == '\\'))
				{
					// void char value (prevents erroneous escapes)
					ch	= undefined;
				}
				else if ((ch == delim) &&
					(lastChar != '\\'))
				{
					// found string end, break out of loop
					result		+= delim;
					lastType	= 'c';
					break;
				}
				else
					result	+= ch;

				lastChar = ch;
			}
			lastType	= 'c';
		}
		// testing code ranges is about 4 percent
		// faster, on average, than using indexOf
		// on a string containing code chars
		else if (((code > 34) && (code < 39))	||	// # $ % &
			((code > 39) && (code < 48)) 		||	// ( ) * + , - . /
			((code > 57) && (code < 65)) 		||	// : ; < = > ? @
			((code > 90) && (code < 97)) 		||	// [ \ ] ^ _ `
			((code > 122) && (code < 127)) 		||	// { \ } ~
			(code == 33))							// !
		{
			// code char, add it
			result		+= src.charAt(i++);
			lastType	= 'c';
		}
		// testing whitespace code range is about 7
		// percent faster, on average, than testing
		// individual whitespace chars
		else if (((code > 8) &&
				 (code < 14)) ||
				 (code == 32))
		{
			// white char, skip it
			i++;
			while (i < len)
			{
				code	= src.charCodeAt(i);
				if (((code > 8) &&
					(code < 14)) ||
					(code == 32))
					i++;
				else
					 break;
			}

			if (lastType != 'c')
				lastType	= 'w';
		}
		else
		{
			if (lastType == 'w')
				result		+= ' ';

			result		+= src.charAt(i++);
			lastType	= 'n';
		}
	}

	return result;
}

function Base64EncodeString(inString)
{
	var str			= inString;
	var remainder	= str.length % 3;
	var tripleEnd	= Math.floor(str.length / 3) * 3;
	var result		= "";
	var pad			= '=';
	var i			= 0;

	// process complete triples
	while (i < tripleEnd)
	{
		result	+= String.fromCharCode(Base64EncodeChar((str.charCodeAt(i) >> 2) & 0x3F));
		result	+= String.fromCharCode(Base64EncodeChar((str.charCodeAt(i) << 4) & 0x30 | (str.charCodeAt(++i) >> 4) & 0x0F));
		result	+= String.fromCharCode(Base64EncodeChar((str.charCodeAt(i) << 2) & 0x3C | (str.charCodeAt(++i) >> 6) & 0x03));
		result	+= String.fromCharCode(Base64EncodeChar( str.charCodeAt(i) & 0x3F));
		i++;
	}

	// process triple fragments
	if (remainder == 2)
	{
		// add one pad char
		result	+= String.fromCharCode(Base64EncodeChar((str.charCodeAt(i) >> 2) & 0x3F));
		result	+= String.fromCharCode(Base64EncodeChar((str.charCodeAt(i) << 4) & 0x30 | (str.charCodeAt(++i) >> 4) & 0x0F));
		result	+= String.fromCharCode(Base64EncodeChar((str.charCodeAt(i) << 2) & 0x3C));
		result	+= pad;
	}
	else if (remainder == 1)
	{
		// add two pad chars
		result	+= String.fromCharCode(Base64EncodeChar((str.charCodeAt(i) >> 2) & 0x3F));
		result	+= String.fromCharCode(Base64EncodeChar((str.charCodeAt(i) << 4) & 0x30));
		result	+= pad;
		result	+= pad;
	}

	return result;
}

function Base64EncodeChar(inChar)
{
	if (inChar < 26)			// A-Z	= 0 - 25
		return inChar + 65;
	else if (inChar < 52)		// a-z	= 26 - 51
		return inChar + 71;
	else if (inChar < 62) 		// 0-9	= 52 - 61
		return inChar - 4;
	else if (inChar == 62)		// '+'
		return 43;
	else
		return 47;				// '/'
}

function Base64DecodeString(inString)
{
	var str			= inString;
	var tlen		= str.length;
	var pad1		= '=';
	var pad2		= ' ';
	var result		= "";
	var i			= 0;
	var code1, code2;

	while (i < tlen)
	{
		// build the first char
		code1	= (Base64DecodeChar(str.charCodeAt(i))		<< 2) & 0xFC;
		code2	= (Base64DecodeChar(str.charCodeAt(++i))	>> 4) & 0x03;
		result 	+=  String.fromCharCode(code1 | code2);
		if ((str.charAt(i + 1) == pad1) || (str.charAt(i + 1) == pad2)) break;

		// build the second char
		code1	= (Base64DecodeChar(str.charCodeAt(i))		<< 4) & 0xF0;
		code2	= (Base64DecodeChar(str.charCodeAt(++i))	>> 2) & 0x0F;
		result 	+= String.fromCharCode(code1 | code2);
		if ((str.charAt(i + 1) == pad1) || (str.charAt(i + 1) == pad2)) break;

		// build the third char
		code1	= (Base64DecodeChar(str.charCodeAt(i))		<< 6) & 0xC0;
		code2	=  Base64DecodeChar(str.charCodeAt(++i)) 		  & 0x3F;
		result 	+= String.fromCharCode(code1 | code2);

		i++;
	}

	return result;
}

function Base64DecodeChar(inChar)
{
	if (inChar == 43)			// '+'
		return 62;
	else if (inChar == 47)		// '/'
		return 63;
	else if (inChar < 58) 		// 0-9	= 52 - 61
		return inChar + 4;
	else if (inChar < 91)		// A-Z	= 0 - 25
		return inChar - 65;
	else
		return inChar - 71;		// a-z	= 26 - 51
}
<|endoftext|>


<commit_before>$(document).ready(function(){
	$.getJSON("/google-adsense.config.json", function (data){
			//If ad in header is enabled
			if(data.header_active == "on"){
				$(getInsCode(data.client_id, data.header_id, 'container', 'margin:0 auto;', 'auto')).insertBefore("#content");
				(adsbygoogle = window.adsbygoogle || []).push({});
			}

			//If ad in footer is enabled
			if(data.footer_active == "on"){
				$(getInsCode(data.client_id, data.footer_id, 'container', 'margin:0 auto;', 'auto')).insertBefore("footer");
				(adsbygoogle = window.adsbygoogle || []).push({});
			}
			$(window).on('action:ajaxify.end', function (e, url) {

				if (url.url.substring(0, 6) == "topic/") {
					if(data.first_post_active == "on"){
						switch(data.first_post_position){
							case 'bottom':
							$(".post-content").first().append(getInsCode(data.client_id, data.footer_id, '', 'margin:0 auto;', 'auto'));
							break;

							case 'top':
							$(".post-content").first().prepend(getInsCode(data.client_id, data.footer_id, '', 'margin:0 auto;', 'auto'));
							break;

							case 'left':
							var height = $(".post-content").first().height();
							if(height < 250){
								var type = "auto";
								var width = 250;
							}
							else{
								var width = 300;
								var type ="vertical";
							}
							$(".post-content").first().prepend(getInsCode(data.client_id, data.footer_id, 'pull-left', 'width:' + width + 'px;  margin-right:30px;', type));
							break;

							case 'right':
							var height = $(".post-content").first().height();
							if(height < 250){
								var type = "auto";
								var width = 250;
							}
							else{
								var width = 300;
								var type ="vertical";
							}
							$(".post-content").first().prepend(getInsCode(data.client_id, data.footer_id, 'pull-right', 'width:' + width + 'px; margin-left:30px;', type));
							break;

							default:
							break;
						}
						(adsbygoogle = window.adsbygoogle || []).push({});
					}
				}
			})
			(adsbygoogle = window.adsbygoogle || []).push({});
		})

})

function getInsCode(clientId, dataId, customClass, style, format){
	var ad = '';
	return ad;
}<commit_msg>fixes<commit_after>$(document).ready(function(){
	$.getJSON("/google-adsense.config.json", function (data){
			//If ad in header is enabled
			if(data.header_active == "on"){
				$(getInsCode(data.client_id, data.header_id, 'container', 'margin:0 auto;', 'auto')).insertBefore("#content");
				(adsbygoogle = window.adsbygoogle || []).push({});
			}

			//If ad in footer is enabled
			if(data.footer_active == "on"){
				$(getInsCode(data.client_id, data.footer_id, 'container', 'margin:0 auto;', 'auto')).insertBefore("footer");
				(adsbygoogle = window.adsbygoogle || []).push({});
			}
			$(window).on('action:ajaxify.end', function (e, url) {

				if (url.url.substring(0, 6) == "topic/") {
					if(data.first_post_active == "on"){
						switch(data.first_post_position){
							case 'bottom':
							$(".post-content").first().append(getInsCode(data.client_id, data.footer_id, '', 'margin:0 auto;', 'auto'));
							break;

							case 'top':
							$(".post-content").first().prepend(getInsCode(data.client_id, data.footer_id, '', 'margin:0 auto;', 'auto'));
							break;

							case 'left':
							var height = $(".post-content").first().height();
							if(height < 250){
								var type = "rectangle";
								var width = 250;
							}
							else{
								var width = 300;
								var type ="vertical";
							}
							$(".post-content").first().prepend(getInsCode(data.client_id, data.footer_id, 'pull-left', 'width:' + width + 'px;  margin-right:30px;', type));
							break;

							case 'right':
							var height = $(".post-content").first().height();
							if(height < 250){
								var type = "rectangle";
								var width = 250;
							}
							else{
								var width = 300;
								var type ="vertical";
							}
							$(".post-content").first().prepend(getInsCode(data.client_id, data.footer_id, 'pull-right', 'width:' + width + 'px; margin-left:30px;', type));
							break;

							default:
							break;
						}
						(adsbygoogle = window.adsbygoogle || []).push({});
					}
				}
			})
			(adsbygoogle = window.adsbygoogle || []).push({});
		})

})

function getInsCode(clientId, dataId, customClass, style, format){
	var ad = '';
	return ad;
}<|endoftext|>


<commit_before>'use strict'

const Pair = require('./pair')

function makePairs (students, allPreviousPairs) {
  if (!(students && students.length)) {
    throw new Error('No students')
  }

  // To satisfy tests that pass in duplicate students, remove any duplicates before proceeding.
  students = students.filter(removeDuplicates)

  if (students.length <= 3) {
    return [(new Pair(...students))]
  }

  if (!(allPreviousPairs && allPreviousPairs.length)) {
    allPreviousPairs = []
  }

  return students.reduce(studentsToPairs(allPreviousPairs), [])
}

function studentsToPairs (allPreviousPairs) {
  return function reducer (accumulator, currentStudent, index, allStudents) {
    const matchedStudents = accumulator.reduce(pairsToStudents, [])

    if (matchedStudents.indexOf(currentStudent) === -1) {
      const previousPairs = findPreviousPairsForStudent(currentStudent, allPreviousPairs)
      const unmatchedStudents = allStudents.filter((student) => matchedStudents.indexOf(student) === -1)

      const newPair = unmatchedStudents.length <= 3
        ? new Pair(...unmatchedStudents)
        : findPairForStudent(currentStudent, previousPairs, unmatchedStudents);

      accumulator.push(newPair)
    }

    return accumulator
  }
}

function findPreviousPairsForStudent (currentStudent, allPreviousPairs) {
  // You may be tempted sort the list first!! Don't! It will break the algorithm.
  return allPreviousPairs
    .filter((pair) => pair.members.indexOf(currentStudent) > -1)
    .reduce(pairsToStudents, [])
    .filter(removeDuplicates) // optimization not under test - tests will not fail if removed
    .filter((student) => student !== currentStudent)
}

function findPairForStudent (currentStudent, previousPairs, students) {

  if (students.length <= 3) {
    return (new Pair(...students))
  }

  const possiblePairs = students
    .filter((student) => student !== currentStudent)
    .filter((student) => previousPairs.indexOf(student) === -1)

  const secondMember = possiblePairs.length ? possiblePairs[0] : students[1]

  return new Pair(currentStudent, secondMember)
}

function pairsToStudents (students, pair) {
  return students.concat(pair.members)
}

function removeDuplicates (student, index, source) {
  return source.indexOf(student, index + 1) === -1
}

module.exports = makePairs
<commit_msg>style(pairs): rename accumlator to pairs for clairity<commit_after>'use strict'

const Pair = require('./pair')

function makePairs (students, allPreviousPairs) {
  if (!(students && students.length)) {
    throw new Error('No students')
  }

  // To satisfy tests that pass in duplicate students, remove any duplicates before proceeding.
  students = students.filter(removeDuplicates)

  if (students.length <= 3) {
    return [(new Pair(...students))]
  }

  if (!(allPreviousPairs && allPreviousPairs.length)) {
    allPreviousPairs = []
  }

  return students.reduce(studentsToPairs(allPreviousPairs), [])
}

function studentsToPairs (allPreviousPairs) {
  return function reducer (pairs, currentStudent, index, allStudents) {
    const matchedStudents = pairs.reduce(pairsToStudents, [])

    if (matchedStudents.indexOf(currentStudent) === -1) {
      const previousPairs = findPreviousPairsForStudent(currentStudent, allPreviousPairs)
      const unmatchedStudents = allStudents.filter((student) => matchedStudents.indexOf(student) === -1)

      const newPair = unmatchedStudents.length <= 3
        ? new Pair(...unmatchedStudents)
        : findPairForStudent(currentStudent, previousPairs, unmatchedStudents);

      pairs.push(newPair)
    }

    return pairs
  }
}

function findPreviousPairsForStudent (currentStudent, allPreviousPairs) {
  // You may be tempted sort the list first!! Don't! It will break the algorithm.
  return allPreviousPairs
    .filter((pair) => pair.members.indexOf(currentStudent) > -1)
    .reduce(pairsToStudents, [])
    .filter(removeDuplicates) // optimization not under test - tests will not fail if removed
    .filter((student) => student !== currentStudent)
}

function findPairForStudent (currentStudent, previousPairs, students) {

  if (students.length <= 3) {
    return (new Pair(...students))
  }

  const possiblePairs = students
    .filter((student) => student !== currentStudent)
    .filter((student) => previousPairs.indexOf(student) === -1)

  const secondMember = possiblePairs.length ? possiblePairs[0] : students[1]

  return new Pair(currentStudent, secondMember)
}

function pairsToStudents (students, pair) {
  return students.concat(pair.members)
}

function removeDuplicates (student, index, source) {
  return source.indexOf(student, index + 1) === -1
}

module.exports = makePairs
<|endoftext|>


<commit_before>var getLongenedURL = function (url, callback) {
  var req = new XMLHttpRequest();
  req.onload = function () {
    callback(window.location + this.responseText);
  }

  req.open('GET', '/create?url=' + url)
  req.send()
}

document.getElementById('new_url').onsubmit = function () {
  var result_url = document.getElementById('result_url');
  var result     = document.getElementById('result');

  result_url.value = 'loading...';
  result.style.display = 'block';

  getLongenedURL(document.getElementById('new_url_url').value, function (url) {
    result_url.value = url;
    result_url.select();
  });

  return false;
}
<commit_msg>Fixes Issue #1<commit_after>var getLongenedURL = function (url, callback) {
  var req = new XMLHttpRequest();
  req.onload = function () {
    callback(window.location + this.responseText);
  }

  req.open('GET', '/create?url=' + encodeURIComponent(url))
  req.send()
}

document.getElementById('new_url').onsubmit = function () {
  var result_url = document.getElementById('result_url');
  var result     = document.getElementById('result');

  result_url.value = 'loading...';
  result.style.display = 'block';

  getLongenedURL(document.getElementById('new_url_url').value, function (url) {
    result_url.value = url;
    result_url.select();
  });

  return false;
}
<|endoftext|>


<commit_before>// JSON.prune : a function to stringify any object without overflow
// two additional optional parameters :
//   - the maximal depth (default : 6)
//   - the maximal length of arrays (default : 50)
// You can also pass an "options" object.
// examples :
//   var json = JSON.prune(window)
//   var arr = Array.apply(0,Array(1000)); var json = JSON.prune(arr, 4, 20)
//   var json = JSON.prune(window.location, {inheritedProperties:true})
// Web site : http://dystroy.org/JSON.prune/
// JSON.prune on github : https://github.com/Canop/JSON.prune
// This was discussed here : http://stackoverflow.com/q/13861254/263525
// The code is based on Douglas Crockford's code : https://github.com/douglascrockford/JSON-js/blob/master/json2.js
// No effort was done to support old browsers. JSON.prune will fail on IE8.
(function () {
	'use strict';

	var DEFAULT_MAX_DEPTH = 6;
	var DEFAULT_ARRAY_MAX_LENGTH = 50;
	var DEFAULT_PRUNED_VALUE = '"-pruned-"';
	var seen; // Same variable used for all stringifications
	var iterator; // either forEachEnumerableOwnProperty, forEachEnumerableProperty or forEachProperty

	// iterates on enumerable own properties (default behavior)
	var forEachEnumerableOwnProperty = function(obj, callback) {
		for (var k in obj) {
			if (Object.prototype.hasOwnProperty.call(obj, k)) callback(k);
		}
	};
	// iterates on enumerable properties
	var forEachEnumerableProperty = function(obj, callback) {
		for (var k in obj) callback(k);
	};
	// iterates on properties, even non enumerable and inherited ones
	// This is dangerous
	var forEachProperty = function(obj, callback, excluded) {
		if (obj==null) return;
		excluded = excluded || {};
		Object.getOwnPropertyNames(obj).forEach(function(k){
			if (!excluded[k]) {
				callback(k);
				excluded[k] = true;
			}
		});
		forEachProperty(Object.getPrototypeOf(obj), callback, excluded);
	};

	Date.prototype.toPrunedJSON = Date.prototype.toJSON;
	String.prototype.toPrunedJSON = String.prototype.toJSON;

	var	cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
		escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
		meta = {	// table of character substitutions
			'\b': '\\b',
			'\t': '\\t',
			'\n': '\\n',
			'\f': '\\f',
			'\r': '\\r',
			'"' : '\\"',
			'\\': '\\\\'
		};

	function quote(string) {
		escapable.lastIndex = 0;
		return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
			var c = meta[a];
			return typeof c === 'string'
				? c
				: '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
		}) + '"' : '"' + string + '"';
	}

	var prune = function (value, depthDecr, arrayMaxLength) {
		var prunedString = DEFAULT_PRUNED_VALUE;
		var replacer;
		if (typeof depthDecr == "object") {
			var options = depthDecr;
			depthDecr = options.depthDecr;
			arrayMaxLength = options.arrayMaxLength;
			iterator = options.iterator || forEachEnumerableOwnProperty;
			if (options.allProperties) iterator = forEachProperty;
			else if (options.inheritedProperties) iterator = forEachEnumerableProperty
			if ("prunedString" in options) {
				prunedString = options.prunedString;
			}
			if (options.replacer) {
				replacer = options.replacer;
			}
		} else {
			iterator = forEachEnumerableOwnProperty;
		}
		seen = [];
		depthDecr = depthDecr || DEFAULT_MAX_DEPTH;
		arrayMaxLength = arrayMaxLength || DEFAULT_ARRAY_MAX_LENGTH;
		function str(key, holder, depthDecr) {
			var i, k, v, length, partial, value = holder[key];
			if (value && typeof value === 'object' && typeof value.toPrunedJSON === 'function') {
				value = value.toPrunedJSON(key);
			}

			switch (typeof value) {
			case 'string':
				return quote(value);
			case 'number':
				return isFinite(value) ? String(value) : 'null';
			case 'boolean':
			case 'null':
				return String(value);
			case 'object':
				if (!value) {
					return 'null';
				}
				if (depthDecr<=0 || seen.indexOf(value)!==-1) {
					if (replacer) {
						var replacement = replacer(value, prunedString, true);
						return replacement===undefined ? undefined : ''+replacement;
					}
					return prunedString;
				}
				seen.push(value);
				partial = [];
				if (Object.prototype.toString.apply(value) === '[object Array]') {
					length = Math.min(value.length, arrayMaxLength);
					for (i = 0; i < length; i += 1) {
						partial[i] = str(i, value, depthDecr-1) || 'null';
					}
					v = '[' + partial.join(',') + ']';
					if (replacer && value.length>arrayMaxLength) return replacer(value, v, false);
					return v;
				}
				iterator(value, function(k) {
					try {
						v = str(k, value, depthDecr-1);
						if (v) partial.push(quote(k) + ':' + v);
					} catch (e) {
						// this try/catch due to forbidden accessors on some objects
					}
				});
				return '{' + partial.join(',') + '}';
			case 'function':
			case 'undefined':
				return replacer ? replacer(value, undefined, false) : undefined;
			}
		}
		return str('', {'': value}, depthDecr);
	};

	prune.log = function() {
		console.log.apply(console, Array.prototype.map.call(arguments, function(v) {
			return JSON.parse(JSON.prune(v));
		}));
	};
	prune.forEachProperty = forEachProperty; // you might want to also assign it to Object.forEachProperty

	if (typeof module !== "undefined") module.exports = prune;
	else JSON.prune = prune;
}());
<commit_msg>make Date's toPrunedJSON not enumerable<commit_after>// JSON.prune : a function to stringify any object without overflow
// two additional optional parameters :
//   - the maximal depth (default : 6)
//   - the maximal length of arrays (default : 50)
// You can also pass an "options" object.
// examples :
//   var json = JSON.prune(window)
//   var arr = Array.apply(0,Array(1000)); var json = JSON.prune(arr, 4, 20)
//   var json = JSON.prune(window.location, {inheritedProperties:true})
// Web site : http://dystroy.org/JSON.prune/
// JSON.prune on github : https://github.com/Canop/JSON.prune
// This was discussed here : http://stackoverflow.com/q/13861254/263525
// The code is based on Douglas Crockford's code : https://github.com/douglascrockford/JSON-js/blob/master/json2.js
// No effort was done to support old browsers. JSON.prune will fail on IE8.
(function () {
	'use strict';

	var DEFAULT_MAX_DEPTH = 6;
	var DEFAULT_ARRAY_MAX_LENGTH = 50;
	var DEFAULT_PRUNED_VALUE = '"-pruned-"';
	var seen; // Same variable used for all stringifications
	var iterator; // either forEachEnumerableOwnProperty, forEachEnumerableProperty or forEachProperty

	// iterates on enumerable own properties (default behavior)
	var forEachEnumerableOwnProperty = function(obj, callback) {
		for (var k in obj) {
			if (Object.prototype.hasOwnProperty.call(obj, k)) callback(k);
		}
	};
	// iterates on enumerable properties
	var forEachEnumerableProperty = function(obj, callback) {
		for (var k in obj) callback(k);
	};
	// iterates on properties, even non enumerable and inherited ones
	// This is dangerous
	var forEachProperty = function(obj, callback, excluded) {
		if (obj==null) return;
		excluded = excluded || {};
		Object.getOwnPropertyNames(obj).forEach(function(k){
			if (!excluded[k]) {
				callback(k);
				excluded[k] = true;
			}
		});
		forEachProperty(Object.getPrototypeOf(obj), callback, excluded);
	};

	Object.defineProperty(Date.prototype, "toPrunedJSON", {value:Date.prototype.toJSON});

	var	cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
		escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
		meta = {	// table of character substitutions
			'\b': '\\b',
			'\t': '\\t',
			'\n': '\\n',
			'\f': '\\f',
			'\r': '\\r',
			'"' : '\\"',
			'\\': '\\\\'
		};

	function quote(string) {
		escapable.lastIndex = 0;
		return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
			var c = meta[a];
			return typeof c === 'string'
				? c
				: '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
		}) + '"' : '"' + string + '"';
	}

	var prune = function (value, depthDecr, arrayMaxLength) {
		var prunedString = DEFAULT_PRUNED_VALUE;
		var replacer;
		if (typeof depthDecr == "object") {
			var options = depthDecr;
			depthDecr = options.depthDecr;
			arrayMaxLength = options.arrayMaxLength;
			iterator = options.iterator || forEachEnumerableOwnProperty;
			if (options.allProperties) iterator = forEachProperty;
			else if (options.inheritedProperties) iterator = forEachEnumerableProperty
			if ("prunedString" in options) {
				prunedString = options.prunedString;
			}
			if (options.replacer) {
				replacer = options.replacer;
			}
		} else {
			iterator = forEachEnumerableOwnProperty;
		}
		seen = [];
		depthDecr = depthDecr || DEFAULT_MAX_DEPTH;
		arrayMaxLength = arrayMaxLength || DEFAULT_ARRAY_MAX_LENGTH;
		function str(key, holder, depthDecr) {
			var i, k, v, length, partial, value = holder[key];
			if (value && typeof value === 'object' && typeof value.toPrunedJSON === 'function') {
				value = value.toPrunedJSON(key);
			}

			switch (typeof value) {
			case 'string':
				return quote(value);
			case 'number':
				return isFinite(value) ? String(value) : 'null';
			case 'boolean':
			case 'null':
				return String(value);
			case 'object':
				if (!value) {
					return 'null';
				}
				if (depthDecr<=0 || seen.indexOf(value)!==-1) {
					if (replacer) {
						var replacement = replacer(value, prunedString, true);
						return replacement===undefined ? undefined : ''+replacement;
					}
					return prunedString;
				}
				seen.push(value);
				partial = [];
				if (Object.prototype.toString.apply(value) === '[object Array]') {
					length = Math.min(value.length, arrayMaxLength);
					for (i = 0; i < length; i += 1) {
						partial[i] = str(i, value, depthDecr-1) || 'null';
					}
					v = '[' + partial.join(',') + ']';
					if (replacer && value.length>arrayMaxLength) return replacer(value, v, false);
					return v;
				}
				iterator(value, function(k) {
					try {
						v = str(k, value, depthDecr-1);
						if (v) partial.push(quote(k) + ':' + v);
					} catch (e) {
						// this try/catch due to forbidden accessors on some objects
					}
				});
				return '{' + partial.join(',') + '}';
			case 'function':
			case 'undefined':
				return replacer ? replacer(value, undefined, false) : undefined;
			}
		}
		return str('', {'': value}, depthDecr);
	};

	prune.log = function() {
		console.log.apply(console, Array.prototype.map.call(arguments, function(v) {
			return JSON.parse(JSON.prune(v));
		}));
	};
	prune.forEachProperty = forEachProperty; // you might want to also assign it to Object.forEachProperty

	if (typeof module !== "undefined") module.exports = prune;
	else JSON.prune = prune;
}());
<|endoftext|>


<commit_before>// parallax-scrape
//
// Parallax Forum Scraper
//
// Fetch a Parallax forum page and extract posts to a plain text file.
//
"use strict";

var request = require("request");
var htmlparser = require("htmlparser2");

// Parser state.
var state = 'initial';

// Temporary store for tag attributes
var attributes;

// List of attachments to posts found in page
var attachments = {};

// Base part of an attachment URL
var attachmentUrlBase = 'http://forums.parallax.com/attachment.php?attachmentid=';

// Output function. Just write string to standard out.
function output(string) {
    process.stdout.write(string);
}

// Pretty print the post text, max 80 chars per line.
function prettyPrintPost(text, indent) {
    var words = [],
        lineLength = 0,
        word,
        indentStr = '',
        i;

    // Make a string of spaces for indenting with
    indent = indent || 0;
    for (i = 0; i < indent; i += 1) {
        indentStr += ' ';
    }

    // Replace all HTML quote entities
    text = text.replace(/"/g, '"');

    // Replace stupid unicode separator line thingies
    text = text.replace(/▔/g, '_');

    // Split text into words.
    words = text.trim().split(" ");

    output(indentStr);
    lineLength = indent;

    // Output the words of lines of less than maximum length
    for (i = 0; i < words.length; i += 1) {
        word = words[i];
        if (word.length > 0) {
            if ((lineLength + word.length) > 80) {
                output('\n' + indentStr);
                lineLength = indent;
            }
            output(word + ' ');
            lineLength += word.length + 1;
        }
    }
    output('\n');
}

// Pretty print dates
function prettyPrintDate(text) {
    var monthStr,
        dateStr,
        date;

    // Get rid of junk after the actual date text.
    text = text.trim().split(',')[0];

    // Take care of relative dates
    if ((text === 'Yesterday') || (text === 'Today')) {
        date = new Date();
        if (text === 'Yesterday') {
            date.setDate(date.getDate() - 1);
        }
        monthStr = String(date.getMonth() + 1);
        if (monthStr.length === 1) {
            monthStr = '0' + monthStr;
        }
        output(monthStr + '-');
        dateStr = String(date.getDate());
        if (dateStr.length === 1) {
            dateStr = '0' + dateStr;
        }
        output(dateStr + '-');
        output(String(date.getFullYear()));
    } else {
        output(text);
    }
}

function prettyPrintCode(text) {
    var lines,
        i;

    lines = text.split('\n');
    for (i = 0; i < lines.length; i += 1) {
        output('        ');
        output(lines[i] + '\n');
    }
}

function outputSectionBreak() {
    output('\n--------------------------------------------------------------------------------\n');
}

function outputPost(text) {
    prettyPrintPost(text);
}

function outputUser(text) {
    output(text);
    outputSectionBreak();
}

function outputDate(text) {
    outputSectionBreak();
    prettyPrintDate(text);
    output(', ');
}

function outputTime(text) {
    output(text + '\n');
}

function outputCode(text) {
    prettyPrintCode(text);
}

function outputQuote(text) {
    prettyPrintPost(text, 4);
}

function saveAttribs(tagname, attribs) {
    attributes = attribs;
}

function outputAttachment(text) {
    var id,
        name,
        attachment = {};

    id = attributes.href.match(/attachmentid=[0-9]+/)[0];
    id = id.match(/[0-9]+/)[0];
    name = text.split('&lrm')[0];

    if (attachments[name]) {
        console.log("Duplicate attachment name: ", name);
    }
    attachments[name] = id;
    output('Attachment: ' + name + ' id: ' + id  + '\n');
}

function debugOpenTag(tagname, attribs) {
    console.log("Debug:");
    console.log("Tag:    ", tagname);
    console.log("Attribs:", attribs);
}

//  state,    tag,          class,                           action,              next state
var openTagTable = {
    initial: [
        {tag: 'blockquote', class: 'postcontent restore ',   action: undefined,   nextState: 'inpost'},
        {tag: 'span',       class: 'parauser',               action: undefined,   nextState: 'inparauser'},
        {tag: 'span',       class: 'date',                   action: undefined,   nextState: 'indate'},
        {tag: 'div',        class: 'attachments',            action: undefined,   nextState: 'inattachments'}
    ],
    inpost: [
        {tag: 'pre',        class: 'bbcode_code',            action: undefined,   nextState: 'incode'},
        {tag: 'div',        class: 'bbcode_quote',           action: undefined,   nextState: 'inbbcode_quote'}
    ],
    inbbcode_quote: [
        {tag: 'div',        class: 'quote_container',        action: undefined,   nextState: 'inquote_container'}
    ],
    inquote_container: [
        {tag: 'div',        class: 'bbcode_quote_container', action: undefined,   nextState: 'inbbcode_quote_container'},
        {tag: 'div',        class: 'message',                action: undefined,   nextState: 'inmessage'},
        {tag: 'div',        class: 'bbcode_postedby',        action: undefined,   nextState: 'inbbcode_postedby'}
    ],
    inattachments: [
        {tag: 'a',          class: undefined,                action: saveAttribs, nextState: 'inattachment'}
    ],
    indate: [
        {tag: 'span',       class: 'time',                   action: undefined,   nextState: 'intime'}
    ]
};

var textTable = {
    inpost:                   {action: outputPost      },
    inparauser:               {action: outputUser      },
    indate:                   {action: outputDate      },
    intime:                   {action: outputTime      },
    incode:                   {action: outputCode      },
    inquote_container:        {action: outputQuote     },
    inbbcode_postedby:        {action: outputQuote     },
    inmessage:                {action: outputQuote     },
    inattachment:             {action: outputAttachment}
};

//  Curent state,             tag,                next state
var closeTagTable = {
    inpost:                   {tag: 'blockquote', nextState: 'initial'          },
    inparauser:               {tag: 'span',       nextState: 'initial'          },
    indate:                   {tag: 'span',       nextState: 'initial'          },
    intime:                   {tag: 'span',       nextState: 'indate'           },
    incode:                   {tag: 'pre',        nextState: 'inpost'           },
    inbbcode_quote:           {tag: 'div',        nextState: 'inpost'           },
    inquote_container:        {tag: 'div',        nextState: 'inbbcode_quote'   },
    inbbcode_quote_container: {tag: 'div',        nextState: 'inquote_container'},
    inbbcode_postedby:        {tag: 'div',        nextState: 'inquote_container'},
    inmessage:                {tag: 'div',        nextState: 'inquote_container'},
    inattachments:            {tag: 'div',        nextState: 'initial'          },
    inattachment:             {tag: 'a',          nextState: 'inattachments'    }
};

// Create an HTML parser
var parser = new htmlparser.Parser({

    onopentag: function (tagname, attribs) {
        var i,
            entry;

        entry = openTagTable[state];
        if (typeof entry !== 'undefined') {
            for (i = 0; i < entry.length; i += 1) {
                if ((entry[i].tag === tagname) && (entry[i].class === attribs.class)) {
                    if (typeof entry[i].action === 'function') {
                        entry[i].action(tagname, attribs);
                    }
                    state = entry[i].nextState;
                    break;
                }
            }
        }
    },

    ontext: function (text) {
        var entry;

        entry = textTable[state];
        if (typeof entry !== 'undefined') {
            if (typeof entry.action === 'function') {
                entry.action(text);
            }
        }
    },

    onclosetag: function (tagname) {
        var entry;

        entry = closeTagTable[state];
        if (typeof entry !== 'undefined') {
            if (tagname === entry.tag) {
                state = entry.nextState;
            }
        }
    }
});

function displayAttachments() {
    var attachmentUrl, name;

    outputSectionBreak();
    console.log('The following attachments were found:');
    for (name in attachments) {
        if (attachments.hasOwnProperty(name)) {
            console.log(name + " : " + attachmentUrlBase + attachments[name]);
        }
    }
}

function fetchPages(url, firstPage, lastPage) {
    var pageNumber,
        pageUrl;

    if ((typeof (firstPage) === 'number') &&
            (typeof (lastPage) === 'number')) {
        pageNumber = firstPage;
        pageUrl = url + '/page' + pageNumber;

        output('\n' + "Page# " + pageNumber + '\n');

        request(pageUrl, function (error, response, body) {
            var attachmentUrl;

            if (error) {
                console.log(error);
            } else {
                parser.write(body);
                parser.end();

                if (pageNumber < lastPage) {
                    fetchPages(url, pageNumber + 1, lastPage);
                } else {
                    displayAttachments();
                }
            }
        });
    }
}

(function main() {
    // Get forum page's URL from program arguments.
    var url = process.argv[2],
        pageNumber,
        page,
        firstPage,
        lastPage;

    // Default URL for testing.
    url = url || 'http://forums.parallax.com/showthread.php/110804-ZiCog-a-Zilog-Z80-emulator-in-1-Cog';

    // Extract page number from url
    page = url.match(/\/page[0-9]+/);
    if (page) {
        pageNumber = parseInt(page[0].match(/[0-9]+/), 10);
    } else {
        pageNumber = 1;
    }

    // Remove page number from url
    url = url.replace(/\/page[0-9]+/, '');

    // Args 3 and 4 give the range of pages to be fetched.
    if ((typeof (process.argv[3]) !== 'undefined') &&
            (typeof (process.argv[4]) !== 'undefined')) {
        firstPage = parseInt(process.argv[3], 10);
        lastPage = parseInt(process.argv[4], 10);
        fetchPages(url, firstPage, lastPage);
    } else {
        // Otherwise take the page number from the url.
        fetchPages(url, pageNumber, pageNumber);
    }
}());

<commit_msg>Added downloading of all found attachments<commit_after>// parallax-scrape
//
// Parallax Forum Scraper
//
// Fetch a Parallax forum page and extract posts to a plain text file.
//
"use strict";

var request = require("request");
var htmlparser = require("htmlparser2");
var fs = require('fs');

// Parser state.
var state = 'initial';

// Temporary store for tag attributes
var attributes;

// List of attachments to posts found in page
var attachments = {};

// Base part of an attachment URL
var attachmentUrlBase = 'http://forums.parallax.com/attachment.php?attachmentid=';

// Output function. Just write string to standard out.
function output(string) {
    process.stdout.write(string);
}

// Pretty print the post text, max 80 chars per line.
function prettyPrintPost(text, indent) {
    var words = [],
        lineLength = 0,
        word,
        indentStr = '',
        i;

    // Make a string of spaces for indenting with
    indent = indent || 0;
    for (i = 0; i < indent; i += 1) {
        indentStr += ' ';
    }

    // Replace all HTML quote entities
    text = text.replace(/"/g, '"');

    // Replace stupid unicode separator line thingies
    text = text.replace(/▔/g, '_');

    // Split text into words.
    words = text.trim().split(" ");

    output(indentStr);
    lineLength = indent;

    // Output the words of lines of less than maximum length
    for (i = 0; i < words.length; i += 1) {
        word = words[i];
        if (word.length > 0) {
            if ((lineLength + word.length) > 80) {
                output('\n' + indentStr);
                lineLength = indent;
            }
            output(word + ' ');
            lineLength += word.length + 1;
        }
    }
    output('\n');
}

// Pretty print dates
function prettyPrintDate(text) {
    var monthStr,
        dateStr,
        date;

    // Get rid of junk after the actual date text.
    text = text.trim().split(',')[0];

    // Take care of relative dates
    if ((text === 'Yesterday') || (text === 'Today')) {
        date = new Date();
        if (text === 'Yesterday') {
            date.setDate(date.getDate() - 1);
        }
        monthStr = String(date.getMonth() + 1);
        if (monthStr.length === 1) {
            monthStr = '0' + monthStr;
        }
        output(monthStr + '-');
        dateStr = String(date.getDate());
        if (dateStr.length === 1) {
            dateStr = '0' + dateStr;
        }
        output(dateStr + '-');
        output(String(date.getFullYear()));
    } else {
        output(text);
    }
}

function prettyPrintCode(text) {
    var lines,
        i;

    lines = text.split('\n');
    for (i = 0; i < lines.length; i += 1) {
        output('        ');
        output(lines[i] + '\n');
    }
}

function outputSectionBreak() {
    output('\n--------------------------------------------------------------------------------\n');
}

function outputPost(text) {
    prettyPrintPost(text);
}

function outputUser(text) {
    output(text);
    outputSectionBreak();
}

function outputDate(text) {
    outputSectionBreak();
    prettyPrintDate(text);
    output(', ');
}

function outputTime(text) {
    output(text + '\n');
}

function outputCode(text) {
    prettyPrintCode(text);
}

function outputQuote(text) {
    prettyPrintPost(text, 4);
}

function saveAttribs(tagname, attribs) {
    attributes = attribs;
}

function outputAttachment(text) {
    var id,
        name,
        attachment = {};

    id = attributes.href.match(/attachmentid=[0-9]+/)[0];
    id = id.match(/[0-9]+/)[0];
    name = text.split('&lrm')[0];

    if (attachments[name]) {
        console.log("Duplicate attachment name: ", name);
    }
    attachments[name] = id;
    output('Attachment: ' + name + ' id: ' + id  + '\n');
}

function debugOpenTag(tagname, attribs) {
    console.log("Debug:");
    console.log("Tag:    ", tagname);
    console.log("Attribs:", attribs);
}

//  state,    tag,          class,                           action,              next state
var openTagTable = {
    initial: [
        {tag: 'blockquote', class: 'postcontent restore ',   action: undefined,   nextState: 'inpost'},
        {tag: 'span',       class: 'parauser',               action: undefined,   nextState: 'inparauser'},
        {tag: 'span',       class: 'date',                   action: undefined,   nextState: 'indate'},
        {tag: 'div',        class: 'attachments',            action: undefined,   nextState: 'inattachments'}
    ],
    inpost: [
        {tag: 'pre',        class: 'bbcode_code',            action: undefined,   nextState: 'incode'},
        {tag: 'div',        class: 'bbcode_quote',           action: undefined,   nextState: 'inbbcode_quote'}
    ],
    inbbcode_quote: [
        {tag: 'div',        class: 'quote_container',        action: undefined,   nextState: 'inquote_container'}
    ],
    inquote_container: [
        {tag: 'div',        class: 'bbcode_quote_container', action: undefined,   nextState: 'inbbcode_quote_container'},
        {tag: 'div',        class: 'message',                action: undefined,   nextState: 'inmessage'},
        {tag: 'div',        class: 'bbcode_postedby',        action: undefined,   nextState: 'inbbcode_postedby'}
    ],
    inattachments: [
        {tag: 'a',          class: undefined,                action: saveAttribs, nextState: 'inattachment'}
    ],
    indate: [
        {tag: 'span',       class: 'time',                   action: undefined,   nextState: 'intime'}
    ]
};

var textTable = {
    inpost:                   {action: outputPost      },
    inparauser:               {action: outputUser      },
    indate:                   {action: outputDate      },
    intime:                   {action: outputTime      },
    incode:                   {action: outputCode      },
    inquote_container:        {action: outputQuote     },
    inbbcode_postedby:        {action: outputQuote     },
    inmessage:                {action: outputQuote     },
    inattachment:             {action: outputAttachment}
};

//  Curent state,             tag,                next state
var closeTagTable = {
    inpost:                   {tag: 'blockquote', nextState: 'initial'          },
    inparauser:               {tag: 'span',       nextState: 'initial'          },
    indate:                   {tag: 'span',       nextState: 'initial'          },
    intime:                   {tag: 'span',       nextState: 'indate'           },
    incode:                   {tag: 'pre',        nextState: 'inpost'           },
    inbbcode_quote:           {tag: 'div',        nextState: 'inpost'           },
    inquote_container:        {tag: 'div',        nextState: 'inbbcode_quote'   },
    inbbcode_quote_container: {tag: 'div',        nextState: 'inquote_container'},
    inbbcode_postedby:        {tag: 'div',        nextState: 'inquote_container'},
    inmessage:                {tag: 'div',        nextState: 'inquote_container'},
    inattachments:            {tag: 'div',        nextState: 'initial'          },
    inattachment:             {tag: 'a',          nextState: 'inattachments'    }
};

// Create an HTML parser
var parser = new htmlparser.Parser({

    onopentag: function (tagname, attribs) {
        var i,
            entry;

        entry = openTagTable[state];
        if (typeof entry !== 'undefined') {
            for (i = 0; i < entry.length; i += 1) {
                if ((entry[i].tag === tagname) && (entry[i].class === attribs.class)) {
                    if (typeof entry[i].action === 'function') {
                        entry[i].action(tagname, attribs);
                    }
                    state = entry[i].nextState;
                    break;
                }
            }
        }
    },

    ontext: function (text) {
        var entry;

        entry = textTable[state];
        if (typeof entry !== 'undefined') {
            if (typeof entry.action === 'function') {
                entry.action(text);
            }
        }
    },

    onclosetag: function (tagname) {
        var entry;

        entry = closeTagTable[state];
        if (typeof entry !== 'undefined') {
            if (tagname === entry.tag) {
                state = entry.nextState;
            }
        }
    }
});

function displayAttachments() {
    var attachmentUrl, name;

    outputSectionBreak();
    console.log('The following attachments were found:');
    for (name in attachments) {
        if (attachments.hasOwnProperty(name)) {
            console.log(name + " : " + attachmentUrlBase + attachments[name]);
        }
    }
}

function fetchAttachments() {
    var attachmentList = [],
        attachment,
        attachmentUrl,
        name;

    outputSectionBreak();

    // Move attachments into a list so we can iterate over them easily.
    for (name in attachments) {
        if (attachments.hasOwnProperty(name)) {
            attachment = {};
            attachment.name = name;
            attachment.id = attachments[name];
            attachmentList.push(attachment);
        }
    }

    (function fetchAttachment(i) {
        attachmentUrl = attachmentUrlBase + attachmentList[i].id;
        console.log("Fetching attachment: " + attachmentList[i].name + " : " + attachmentUrl);
        request(attachmentUrl, function (error, response, body) {
            if (error) {
                console.log("Error fetching attachment: " + attachmentUrl);
            } else {
                fs.writeFile(attachmentList[i].name, body, function (err) {
                    if (err) {
                        console.log(err);
                    } else {
                        if (i < attachmentList.length - 1) {
                            fetchAttachment(i + 1);
                        }
                    }
                });
            }
        });
    }(0));
}

function fetchPages(url, firstPage, lastPage) {
    var pageNumber,
        pageUrl;

    pageNumber = firstPage;
    pageUrl = url + '/page' + pageNumber;

    output('\n' + "Page# " + pageNumber + '\n');

    request(pageUrl, function (error, response, body) {
        var attachmentUrl;

        if (error) {
            console.log(error);
        } else {
            parser.write(body);
            parser.end();

            if (pageNumber < lastPage) {
                fetchPages(url, pageNumber + 1, lastPage);
            } else {
                displayAttachments();
                fetchAttachments();
            }
        }
    });
}

(function main() {
    // Get forum page's URL from program arguments.
    var url = process.argv[2],
        pageNumber,
        page,
        firstPage,
        lastPage;

    // Default URL for testing.
    url = url || 'http://forums.parallax.com/showthread.php/110804-ZiCog-a-Zilog-Z80-emulator-in-1-Cog';

    // Extract page number from url
    page = url.match(/\/page[0-9]+/);
    if (page) {
        pageNumber = parseInt(page[0].match(/[0-9]+/), 10);
    } else {
        pageNumber = 1;
    }

    // Remove page number from url
    url = url.replace(/\/page[0-9]+/, '');

    // Args 3 and 4 give the range of pages to be fetched.
    if ((typeof (process.argv[3]) !== 'undefined') &&
            (typeof (process.argv[4]) !== 'undefined')) {
        firstPage = parseInt(process.argv[3], 10);
        lastPage = parseInt(process.argv[4], 10);
        fetchPages(url, firstPage, lastPage);
    } else {
        // Otherwise take the page number from the url.
        fetchPages(url, pageNumber, pageNumber);
    }
}());

<|endoftext|>