<commit_before>(function ($) {
    jQuery.fn.saiForm = function () {

        /* Select */

        var firstClick = [];
        var newSelect;
        $(".saiForm").find("select").each(function (index) {
            firstClick[index] = true;
            var select = $(this);

            select.before("<input type='hidden' name='select" + (index + 1) + "'/>");
            var inputSelect = select.prev();
            if ($(select).attr("required") != undefined) {
                inputSelect.addClass("required");
            }
            var options = select.find("option");

            inputSelect.wrap("

").wrap("

");

            if (inputSelect.attr("placeholder") != undefined) {
                inputSelect.after("
" + inputSelect.attr("placeholder") + "
");
            } else {
                inputSelect.after("

");
            }
            if (inputSelect.attr("required") != undefined) {
                inputSelect.addClass("required");
            }

            inputSelect.closest(".input_wrapper").after("

");

            for (i = 0; i < options.length; i++) {
                if ($(options[i]).attr("selected") != undefined) {
                    inputSelect.closest(".input_wrapper").next().append("
" + $(options[i]).text() + "
");
                    $(inputSelect).next().text($(options[i]).text());
                } else {
                    inputSelect.closest(".input_wrapper").next().append("
" + $(options[i]).text() + "
");
                }
            }

            inputSelect.next().data("index", index);
            newSelect = inputSelect.closest(".input_wrapper").next();
            $(select).remove();
        });
        $(".input_wrapper .layer").on("click", function (e) {
            var ind = $(this).data("index");
            var thisSelect = $(this).parent().next(".select");
            var ths = $(this);

            if (thisSelect.css('display') != 'block') {
                thisSelect.fadeIn();
                ths.addClass("active");
                firstClick[ind] = true;
                $(document).bind('click.select' + ind + '', function () {
                    if (!firstClick[ind]) {
                        thisSelect.fadeOut();
                        ths.removeClass("active");
                        $(document).unbind('click.select' + ind + '');
                    }
                    firstClick[ind] = false;
                });
            }
            e.preventDefault();
        });

        $(".saiForm .select li").on("click", function () {
            $(this).parent().fadeOut();
            $(this).parent().parent().find(".layer").removeClass("active");
            $(this).parent().prev().find("input").removeAttr("value");
            if (!$(this).hasClass("selected")) {
                $(this).parent().prev().find("input").val($(this).attr("data-value"));
            }
            $(this).parent().prev().find("p").text($(this).text());
            for (i = 0; i < firstClick.length; i++) {
                $(document).unbind('click.select' + i + '');
                firstClick[i] = false;
            }
        });

        function valid_select() {
            var inputs = $(".saiForm").find(".select_wrapper .required");
            var end = true;
            for (i = 0; i < inputs.length; i++) {
                if ($(inputs[i]).val() == false) {
                    $(inputs[i]).next().addClass("false_required");
                    if (!$(inputs[i]).closest(".select_wrapper").next(".message_false").length) {
                        $(inputs[i]).closest(".select_wrapper").after("
Выберите пол
");
                    } else {
                        $(inputs[i]).closest(".select_wrapper").next().text("Выберите пол");
                    }
                    $(inputs[i]).closest(".select_wrapper").next(".message_false").fadeIn();
                    end = false;
                } else {
                    $(inputs[i]).next().removeClass("false_required");
                    $(inputs[i]).closest(".select_wrapper").next(".message_false").remove();
                }
            }
        }
        /* end */

        /* All input */

        $(".saiForm").find("input[required], select[required]").removeAttr("required").addClass("required");

        /* Input text */
        $(".saiForm").find("input[type='text']").addClass("input_text");

        function valid_text() {
            var inputs = $(".saiForm").find(".input_text.required");
            var end = true;
            for (i = 0; i < inputs.length; i++) {
                if ($(inputs[i]).val() == false) {
                    $(inputs[i]).addClass("false_required");
                    if (!$(inputs[i]).next(".message_false").length) {
                        $(inputs[i]).after("
Введите " + $(inputs[i]).attr("placeholder") + "
");
                    } else {
                        $(inputs[i]).next().text("Введите " + $(inputs[i]).attr("placeholder") + "");
                    }
                    $(inputs[i]).next(".message_false").fadeIn();
                    end = false;
                } else {
                    $(inputs[i]).removeClass("false_required");
                    $(inputs[i]).next(".message_false").remove();
                }
            }
        }
        /* End */

        /* End */

        /* E-Mail */

        if ($(".saiForm").find("input[type='email']").length != false) {

            $(".saiForm").find("input[type='email']").each(function () {
                $(this).attr("type", "text").addClass("email");
            });

        }

        function valid_email() {
            var inputs = $(".saiForm").find(".email.required");
            var end = true;
            for (i = 0; i < inputs.length; i++) {
                if ($(inputs[i]).val() == false) {
                    $(inputs[i]).addClass("false_required");
                    if (!$(inputs[i]).next(".message_false").length) {
                        $(inputs[i]).after("
Введите Email
");
                    } else {
                        $(inputs[i]).next().text("Введите Email");
                    }
                    $(inputs[i]).next(".message_false").fadeIn();
                    end = false;
                } else if (!/^\w+\.?\-?\_?\w+@\w+\.\w{2,4}$/i.test($(inputs[i]).val())) {
                    $(inputs[i]).addClass("false_required");
                    if (!$(inputs[i]).next(".message_false").length) {
                        $(inputs[i]).after("
Проверте правильность ввода , допустимые символы ' . ' , ' - ' , ' _ '
");
                    }
                    $(inputs[i]).next(".message_false").fadeIn();
                    end = false;
                } else {
                    $(inputs[i]).removeClass("false_required");
                    $(inputs[i]).next(".message_false").remove();
                }
            }
            console.log("email = " + end);
            return end;
        }
        /* End */

        /* Number */
        if ($(".saiForm").find("input[type='number']").length != false) {
            var numberCount = 0;
            $(".saiForm").find("input[type='number']").each(function () {
                $(this).attr("type", "text").attr("readonly", "").wrap("

");
                $(this).parent().prepend("");
                $(this).parent().append("");
                $(".number").on("click", ".up", function () {
                    numberCount++;
                    $(this).parent().find("input").val(numberCount);
                });
                $(".number").on("click", ".down", function () {
                    if ($(this).parent().find("input").val() != 0) {
                        numberCount--;
                        $(this).parent().find("input").val(numberCount);
                    }
                });
            });

        }
        /* End */

        /* Phone */

        if ($(".saiForm").find("input[type='tel']").length != false) {

            $(".saiForm").find("input[type='tel']").each(function () {
                $(this).attr("type", "text").addClass("phone");
            });
        }

        function valid_tel() {
            var inputs = $(".saiForm").find(".phone.required");
            var end = true;
            for (i = 0; i < inputs.length; i++) {
                if ($(inputs[i]).val() == false) {
                    $(inputs[i]).addClass("false_required");
                    if (!$(inputs[i]).next(".message_false").length) {
                        $(inputs[i]).after("
Введите номер телефона
");
                    } else {
                        $(inputs[i]).next().text("Введите номер телефона");
                    }
                    $(inputs[i]).next(".message_false").fadeIn();
                    end = false;
                } else if (!/^\+/i.test($(inputs[i]).val())) {
                    console.log("tue");
                    $(inputs[i]).addClass("false_required");
                    if (!$(inputs[i]).next(".message_false").length) {
                        $(inputs[i]).after("
В начале должен быть '+'
");
                    } else {
                        $(inputs[i]).next().text("В начале должен быть '+'");
                    }
                    $(inputs[i]).next(".message_false").fadeIn();
                    end = false;
                } else if (!/^\+[0-9\s()-]+$/i.test($(inputs[i]).val())) {
                    $(inputs[i]).addClass("false_required");
                    if (!$(inputs[i]).next(".message_false").length) {
                        $(inputs[i]).after("
Допустимые символы '0-9 ( ) - '
");
                    } else {
                        $(inputs[i]).next().text("Допустимые символы '0-9 ( ) - ' ");
                    }
                    $(inputs[i]).next(".message_false").fadeIn();
                    end = false;
                } else if ($(inputs[i]).val().match(/[0-9]/gi).length < 10) {
                    $(inputs[i]).addClass("false_required");
                    if (!$(inputs[i]).next(".message_false").length) {
                        $(inputs[i]).after("
Минимальное число 10
");
                    } else {
                        $(inputs[i]).next().text("Минимальное число 10");
                    }
                    $(inputs[i]).next(".message_false").fadeIn();
                    end = false;
                } else {
                    $(inputs[i]).removeClass("false_required");
                    $(inputs[i]).next(".message_false").remove();
                }
            }
            console.log("tel = " + end);
            return end;
        }
        /* End */

        /* Radio */

        if ($(".saiForm").find("input[type='radio']").length != false) {

            $(".saiForm").find("input[type='radio']").each(function (index) {
                index = index + 1;

                $(this).wrap("

");
                if ($(this).attr("id") != undefined) {
                    $(this).attr("id", $(this).attr("id") + " radio" + index + "");
                } else {
                    $(this).attr("id", "radio" + index + "");
                }
                $(this).after("");

            });
        }

        /* End */

        /* CheckBox */

        if ($(".saiForm").find("input[type='checkbox']").length != false) {

            $(".saiForm").find("input[type='checkbox']").each(function (index) {
                index = index + 1;
                $(this).wrap("

");
                if ($(this).attr("id") != undefined) {
                    $(this).attr("id", $(this).attr("id") + " checkbox" + index++ + "");
                } else {
                    $(this).attr("id", "checkbox" + index + "");
                }
                $(this).after("");

            });
        }

        /* End */

        /* File */

        if ($(".saiForm").find("input[type='file']").length != false) {

            $(".saiForm").find("input[type='file']").each(function (index) {
                index = index + 1;
                $(this).wrap("

");
                if ($(this).attr("id") != undefined) {
                    $(this).attr("id", $(this).attr("id") + " file" + index++ + "");
                } else {
                    $(this).attr("id", "file" + index + "");
                }
                $(this).after("Browse...");
                $(this).parent().append("");
                $(this).on("change", function () {
                    $(this).parent().find("span").text($(this).val());
                });
            });
        }

        /* End */

        /* Submit */
        $(".saiForm").on("submit", function () {
            var valid = true;
            valid_email();
            valid_tel();
            valid_select();
            valid_text();
            return false;
        });
        /* End */
    };
})(jQuery);<commit_msg>master<commit_after>(function ($) {
    jQuery.fn.saiForm = function () {

        /* Select */

        var firstClick = [];
        var newSelect;
        $(".saiForm").find("select").each(function (index) {
            firstClick[index] = true;
            var select = $(this);

            select.before("<input type='hidden' name='select" + (index + 1) + "'/>");
            var inputSelect = select.prev();
            if ($(select).attr("required") != undefined) {
                inputSelect.addClass("required");
            }
            var options = select.find("option");

            inputSelect.wrap("

").wrap("

");

            if (inputSelect.attr("placeholder") != undefined) {
                inputSelect.after("
" + inputSelect.attr("placeholder") + "
");
            } else {
                inputSelect.after("

");
            }
            if (inputSelect.attr("required") != undefined) {
                inputSelect.addClass("required");
            }

            inputSelect.closest(".input_wrapper").after("

");

            for (i = 0; i < options.length; i++) {
                if ($(options[i]).attr("selected") != undefined) {
                    inputSelect.closest(".input_wrapper").next().append("
" + $(options[i]).text() + "
");
                    $(inputSelect).next().text($(options[i]).text());
                } else {
                    inputSelect.closest(".input_wrapper").next().append("
" + $(options[i]).text() + "
");
                }
            }

            inputSelect.next().data("index", index);
            newSelect = inputSelect.closest(".input_wrapper").next();
            $(select).remove();
        });
        $(".input_wrapper .layer").on("click", function (e) {
            var ind = $(this).data("index");
            var thisSelect = $(this).parent().next(".select");
            var ths = $(this);

            if (thisSelect.css('display') != 'block') {
                thisSelect.fadeIn();
                ths.addClass("active");
                firstClick[ind] = true;
                $(document).bind('click.select' + ind + '', function () {
                    if (!firstClick[ind]) {
                        thisSelect.fadeOut();
                        ths.removeClass("active");
                        $(document).unbind('click.select' + ind + '');
                    }
                    firstClick[ind] = false;
                });
            }
            e.preventDefault();
        });

        $(".saiForm .select li").on("click", function () {
            $(this).parent().fadeOut();
            $(this).parent().parent().find(".layer").removeClass("active");
            $(this).parent().prev().find("input").removeAttr("value");
            if (!$(this).hasClass("selected")) {
                $(this).parent().prev().find("input").val($(this).attr("data-value"));
            }
            $(this).parent().prev().find("p").text($(this).text());
            for (i = 0; i < firstClick.length; i++) {
                $(document).unbind('click.select' + i + '');
                firstClick[i] = false;
            }
        });

        function valid_select() {
            var inputs = $(".saiForm").find(".select_wrapper .required");
            var end = true;
            for (i = 0; i < inputs.length; i++) {
                if ($(inputs[i]).val() == false) {
                    $(inputs[i]).next().addClass("false_required");
                    if (!$(inputs[i]).closest(".select_wrapper").next(".message_false").length) {
                        $(inputs[i]).closest(".select_wrapper").after("
Выберите пол
");
                    } else {
                        $(inputs[i]).closest(".select_wrapper").next().text("Выберите пол");
                    }
                    $(inputs[i]).closest(".select_wrapper").next(".message_false").fadeIn();
                    end = false;
                } else {
                    $(inputs[i]).next().removeClass("false_required");
                    $(inputs[i]).closest(".select_wrapper").next(".message_false").remove();
                }
            }
        }
        /* end */

        /* All input */

        $(".saiForm").find("input[required], select[required]").removeAttr("required").addClass("required");

        /* Input text */
        $(".saiForm").find("input[type='text']").addClass("input_text");

        function valid_text() {
            var inputs = $(".saiForm").find(".input_text.required");
            var end = true;
            for (i = 0; i < inputs.length; i++) {
                if ($(inputs[i]).val() == false) {
                    $(inputs[i]).addClass("false_required");
                    if (!$(inputs[i]).next(".message_false").length) {
                        $(inputs[i]).after("
Введите " + $(inputs[i]).attr("placeholder") + "
");
                    } else {
                        $(inputs[i]).next().text("Введите " + $(inputs[i]).attr("placeholder") + "");
                    }
                    $(inputs[i]).next(".message_false").fadeIn();
                    end = false;
                } else {
                    $(inputs[i]).removeClass("false_required");
                    $(inputs[i]).next(".message_false").remove();
                }
            }
        }
        /* End */

        /* End */

        /* E-Mail */

        if ($(".saiForm").find("input[type='email']").length != false) {

            $(".saiForm").find("input[type='email']").each(function () {
                $(this).attr("type", "text").addClass("email");
            });

        }

        function valid_email() {
            var inputs = $(".saiForm").find(".email.required");
            var end = true;
            for (i = 0; i < inputs.length; i++) {
                if ($(inputs[i]).val() == false) {
                    $(inputs[i]).addClass("false_required");
                    if (!$(inputs[i]).next(".message_false").length) {
                        $(inputs[i]).after("
Введите Email
");
                    } else {
                        $(inputs[i]).next().text("Введите Email");
                    }
                    $(inputs[i]).next(".message_false").fadeIn();
                    end = false;
                } else if (!/^\w+\.?\-?\_?\w+@\w+\.\w{2,4}$/i.test($(inputs[i]).val())) {
                    $(inputs[i]).addClass("false_required");
                    if (!$(inputs[i]).next(".message_false").length) {
                        $(inputs[i]).after("
Проверте правильность ввода , допустимые символы ' . ' , ' - ' , ' _ '
");
                    }
                    $(inputs[i]).next(".message_false").fadeIn();
                    end = false;
                } else {
                    $(inputs[i]).removeClass("false_required");
                    $(inputs[i]).next(".message_false").remove();
                }
            }
            console.log("email = " + end);
            return end;
        }
        /* End */

        /* Number */
        if ($(".saiForm").find("input[type='number']").length != false) {
            var numberCount = 0;
            $(".saiForm").find("input[type='number']").each(function () {
                $(this).attr("type", "text").attr("readonly", "").wrap("

");
                $(this).parent().prepend("");
                $(this).parent().append("");
                $(".number").on("click", ".up", function () {
                    numberCount++;
                    $(this).parent().find("input").val(numberCount);
                });
                $(".number").on("click", ".down", function () {
                    if ($(this).parent().find("input").val() != 0) {
                        numberCount--;
                        $(this).parent().find("input").val(numberCount);
                    }
                });
            });
            function valid_number() {
                var inputs = $(".saiForm").find(".number .required");
                var end = true;
                for (i = 0; i < inputs.length; i++) {
                    if ($(inputs[i]).val() == false) {
                        $(inputs[i]).addClass("false_required");
                        if (!$(inputs[i]).parent().next(".message_false").length) {
                            $(inputs[i]).parent().after("
Выберите " + $(inputs[i]).attr("placeholder") + "
");
                        } else {
                            $(inputs[i]).parent().next().text("Выберите " + $(inputs[i]).attr("placeholder") + "");
                        }
                        $(inputs[i]).parent().next(".message_false").fadeIn();
                        end = false;
                    } else {
                        $(inputs[i]).removeClass("false_required");
                        $(inputs[i]).parent().next(".message_false").remove();
                    }
                }
            }
        }
        /* End */

        /* Phone */

        if ($(".saiForm").find("input[type='tel']").length != false) {

            $(".saiForm").find("input[type='tel']").each(function () {
                $(this).attr("type", "text").addClass("phone");
            });
        }

        function valid_tel() {
            var inputs = $(".saiForm").find(".phone.required");
            var end = true;
            for (i = 0; i < inputs.length; i++) {
                if ($(inputs[i]).val() == false) {
                    $(inputs[i]).addClass("false_required");
                    if (!$(inputs[i]).next(".message_false").length) {
                        $(inputs[i]).after("
Введите номер телефона
");
                    } else {
                        $(inputs[i]).next().text("Введите номер телефона");
                    }
                    $(inputs[i]).next(".message_false").fadeIn();
                    end = false;
                } else if (!/^\+/i.test($(inputs[i]).val())) {
                    console.log("tue");
                    $(inputs[i]).addClass("false_required");
                    if (!$(inputs[i]).next(".message_false").length) {
                        $(inputs[i]).after("
В начале должен быть '+'
");
                    } else {
                        $(inputs[i]).next().text("В начале должен быть '+'");
                    }
                    $(inputs[i]).next(".message_false").fadeIn();
                    end = false;
                } else if (!/^\+[0-9\s()-]+$/i.test($(inputs[i]).val())) {
                    $(inputs[i]).addClass("false_required");
                    if (!$(inputs[i]).next(".message_false").length) {
                        $(inputs[i]).after("
Допустимые символы '0-9 ( ) - '
");
                    } else {
                        $(inputs[i]).next().text("Допустимые символы '0-9 ( ) - ' ");
                    }
                    $(inputs[i]).next(".message_false").fadeIn();
                    end = false;
                } else if ($(inputs[i]).val().match(/[0-9]/gi).length < 10) {
                    $(inputs[i]).addClass("false_required");
                    if (!$(inputs[i]).next(".message_false").length) {
                        $(inputs[i]).after("
Минимальное число 10
");
                    } else {
                        $(inputs[i]).next().text("Минимальное число 10");
                    }
                    $(inputs[i]).next(".message_false").fadeIn();
                    end = false;
                } else {
                    $(inputs[i]).removeClass("false_required");
                    $(inputs[i]).next(".message_false").remove();
                }
            }
            console.log("tel = " + end);
            return end;
        }
        /* End */

        /* Radio */

        if ($(".saiForm").find("input[type='radio']").length != false) {

            $(".saiForm").find("input[type='radio']").each(function (index) {
                index = index + 1;

                $(this).wrap("

");
                if ($(this).attr("id") != undefined) {
                    $(this).attr("id", $(this).attr("id") + " radio" + index + "");
                } else {
                    $(this).attr("id", "radio" + index + "");
                }
                $(this).after("");

            });
        }

        /* End */

        /* CheckBox */

        if ($(".saiForm").find("input[type='checkbox']").length != false) {

            $(".saiForm").find("input[type='checkbox']").each(function (index) {
                index = index + 1;
                $(this).wrap("

");
                if ($(this).attr("id") != undefined) {
                    $(this).attr("id", $(this).attr("id") + " checkbox" + index++ + "");
                } else {
                    $(this).attr("id", "checkbox" + index + "");
                }
                $(this).after("");

            });
        }

        /* End */

        /* File */

        if ($(".saiForm").find("input[type='file']").length != false) {

            $(".saiForm").find("input[type='file']").each(function (index) {
                index = index + 1;
                $(this).wrap("

");
                if ($(this).attr("id") != undefined) {
                    $(this).attr("id", $(this).attr("id") + " file" + index++ + "");
                } else {
                    $(this).attr("id", "file" + index + "");
                }
                $(this).after("Browse...");
                $(this).parent().append("");
                $(this).on("change", function () {
                    $(this).parent().find("span").text($(this).val());
                });
            });
        }

        /* End */

        /* Submit */
        $(".saiForm").on("submit", function () {
            var valid = true;
            valid_email();
            valid_tel();
            valid_select();
            valid_text();
            valid_number();
            return false;
        });
        /* End */
    };
})(jQuery);<|endoftext|>


<commit_before>'use strict';

var util = require('util'),
    mdns = require('mdns2'),
    events = require('events'),
    logger = require('./logger'),
    CastDevice = require('./device');

function Scanner(options) {
  var self = this;
  events.EventEmitter.call(this);
  options = options || {};
  this.logger = options.logger || logger.devnull;
  this.timeout = options.timeout || 5000;
  this.reconnect = options.reconnect || {};
  this.browser = mdns.createBrowser(mdns.tcp('googlecast'));
  this.browser
    .on('serviceUp', onServiceUp)
    .on('serviceDown', onServiceDown);
  this.devices = [];

  function onServiceUp(device) {
    if (!device.addresses ||
        device.addresses.length === 0 ||
        !device.name ||
        !device.port ||
        !device.txtRecord ||
        !device.txtRecord.fn ||
        !device.txtRecord.id) {
      self.logger.info('New device discovered but missing some mandatory information',
                       device);
      return;
    }
    // Usually, Chromecast device are using IPv4 only.
    var c = new CastDevice({
      friendlyName: device.txtRecord.fn,
      address: device.addresses[0],
      port: device.port,
      id: device.txtRecord.id,
      logger: self.logger,
      timeout: self.timeout,
      reconnect: self.reconnect
    });
    if (self.devices[c.id]) {
      self.logger.debug('Already online device appeared', device);
      return;
    }
    self.devices[c.id] = c;
    self.emit('online', c);
  }
  function onServiceDown(device) {
    var id = (device.txtRecord || {}).id;
    if (!self.devices[id]) {
      self.logger.debug('Unknown device has disappeared', device);
      return;
    }
    self.emit('offline', self.devices[id]);
    delete self.devices[id];
  }
}
util.inherits(Scanner, events.EventEmitter);

Scanner.prototype.start = function() {
  this.browser.start();
  return this;
};
Scanner.prototype.end = function() {
  this.browser.end();
  return this;
};

module.exports = Scanner;
<commit_msg>scanner: don't create a CastDevice unless we intend to emit it<commit_after>'use strict';

var util = require('util'),
    mdns = require('mdns2'),
    events = require('events'),
    logger = require('./logger'),
    CastDevice = require('./device');

function Scanner(options) {
  var self = this;
  events.EventEmitter.call(this);
  options = options || {};
  this.logger = options.logger || logger.devnull;
  this.timeout = options.timeout || 5000;
  this.reconnect = options.reconnect || {};
  this.browser = mdns.createBrowser(mdns.tcp('googlecast'));
  this.browser
    .on('serviceUp', onServiceUp)
    .on('serviceDown', onServiceDown);
  this.devices = [];

  function onServiceUp(device) {
    if (!device.addresses ||
        device.addresses.length === 0 ||
        !device.name ||
        !device.port ||
        !device.txtRecord ||
        !device.txtRecord.fn ||
        !device.txtRecord.id) {
      self.logger.info('New device discovered but missing some mandatory information',
                       device);
      return;
    }
    // Usually, Chromecast device are using IPv4 only.
    if (self.devices[device.txtRecord.id]) {
      self.logger.debug('Already online device appeared', device);
      return;
    }
    var c = new CastDevice({
      friendlyName: device.txtRecord.fn,
      address: device.addresses[0],
      port: device.port,
      id: device.txtRecord.id,
      logger: self.logger,
      timeout: self.timeout,
      reconnect: self.reconnect
    });
    self.devices[c.id] = c;
    self.emit('online', c);
  }
  function onServiceDown(device) {
    var id = (device.txtRecord || {}).id;
    if (!self.devices[id]) {
      self.logger.debug('Unknown device has disappeared', device);
      return;
    }
    self.emit('offline', self.devices[id]);
    delete self.devices[id];
  }
}
util.inherits(Scanner, events.EventEmitter);

Scanner.prototype.start = function() {
  this.browser.start();
  return this;
};
Scanner.prototype.end = function() {
  this.browser.end();
  return this;
};

module.exports = Scanner;
<|endoftext|>


<commit_before>/* jshint node: true */

'use strict';

/**
 * Schema parsing and loading utilities.
 *
 */

var fs = require('fs'),
    path = require('path'),
    util = require('util');

var f = util.format;

/**
 * Try to load a schema.
 *
 * This method will attempt to load schemas from a file if the schema passed is
 * a string which isn't valid JSON and contains at least one slash.
 *
 */
function load(schema) {
  var obj;
  if (typeof schema == 'string') {
    try {
      obj = JSON.parse(schema);
    } catch (err) {
      if (~schema.indexOf('/')) {
        // This can't be a valid name, so we interpret is as a filepath. This
        // makes is always feasible to read a file, independent of its name
        // (i.e. even if its name is valid JSON), by prefixing it with `./`.
        obj = JSON.parse(fs.readFileSync(schema));
      }
    }
  }
  if (obj === undefined) {
    obj = schema;
  }
  return obj;
}

/**
 * Assemble an IDL file into a decoded schema.
 *
 * attrs: name, namespace, types, messages, (and any other annotations?)
 *
 */
function assemble(fpath, opts, cb) {
  if (!cb && typeof opts == 'function') {
    cb = opts;
    opts = undefined;
  }

  opts = opts || {};
  opts.imports = opts.imports || {};

  var dpath = path.dirname(fpath);
  var attrs = opts.imports[fpath];
  if (attrs) {
    // Already imported, just skip. We don't do any circularity checks because
    // any such errors will be caught at parsing time.
    cb(null, attrs);
    return;
  }
  attrs = opts.imports[fpath] = {types: [], messages: {}};

  var tokenizer, token;
  fs.readFile(fpath, {encoding: 'utf8'}, function (err, str) {
    if (err) {
      cb(err);
      return;
    }
    tokenizer = new Tokenizer(str);
    assemblePreamble(null, null, cb);
  });

  function assemblePreamble(err, importedAttrs) {
    // Merge imported attributes. We do this first to avoid duplicating error
    // checking (we guard it by a branch instead).
    if (importedAttrs) {
      attrs.types = attrs.types.concat(importedAttrs.types);
      Object.keys(importedAttrs.messages).forEach(function (name) {
        if (attrs.messages[name]) {
          err = new Error(f('duplicate message: %s', name));
          return;
        }
        attrs.messages[name] = importedAttrs.messages[name];
      });
    }

    if (err) {
      cb(err);
      return;
    }

    token = tokenizer.next();
    if (token.val === 'import') {
      assembleImport();
      return;
    }

    try {
      while (token.val === '@') {
        assembleAnnotation(attrs);
        token = tokenizer.next();
      }
      if (token.val !== 'protocol') {
        throw tokenizer.error('expected protocol definition');
      }
      assembleProtocol();
      if (tokenizer.next()) {
        throw tokenizer.error('trailing input');
      }
      cb(null, attrs);
    } catch (err) {
      cb(new Error(f('%s when parsing %s', err.message, fpath)));
    }
  }

  function assembleImport() {
    try {
      var importKind = tokenizer.next({id: 'name'}).val;
      var fpath = path.join(dpath, JSON.parse(tokenizer.next({id: 'string'})));
      tokenizer.next({val: ';'});
    } catch (err) {
      cb(err);
      return;
    }

    if (token.val === 'idl') {
      assemble(fpath, opts, assemblePreamble);
    } else {
      fs.readFile(fpath, function (err, str) {
        if (err) {
          cb(err);
          return;
        }

        try {
          var attrs = JSON.parse(str);
        } catch (err) {
          cb(err);
          return;
        }

        switch (importKind) {
        case 'protocol':
          assemblePreamble(null, attrs);
          break;
        case 'types':
          assemblePreamble(null, {types: [attrs]});
          break;
        default:
          assemblePreamble(tokenizer.error('invalid import'));
        }
      });
    }
  }

  function assembleAnnotation() {
    // Annotations are allowed to have names which aren't valid Avro names, we
    // must advance until we hit the first left parenthesis.
    var parts = [];
    var token;
    while ((token = tokenizer.next())) {
      if (token.val === '(') {
        // Done parsing the annotation's name.
        attrs[parts.join('')] = JSON.parse(tokenizer.next({id: 'json'}).val);
        tokenizer.next({val: ')'});
        return;
      }
      parts.push(token.val);
    }
    throw tokenizer.error('unexpected end of annotation');
  }

  function assembleProtocol() {
    attrs.name = tokenizer.next({id: 'name'});
    tokenizer.next({val: '{'});
    var typeAttrs, token;
    while ((token = tokenizer.next())) {
      if (token.val === '}') {
        return;
      }

      // Parse a type or message definition (and potentially any preceding
      // annotations).
      typeAttrs = {};
      while (token && token.val === '@') {
        assembleAnnotation(typeAttrs);
        token = tokenizer.next();
      }

      // TODO: backtick escaping.

      switch (token.val) {
      case 'record':
      case 'error':
      case 'fixed':
      case 'enum':
      case 'union':
      case 'map':
      case 'array':
        // assembleType(typeAttrs, token.val);
        break;
      default:
        // name = assembleMessage(subattrs, token.val);
        // attrs.messages[name] = subattrs;
      }
    }
    throw tokenizer.error('unexpected end of protocol');
  }

  // function assembleMessage(attrs, ) {}

  // function assembleType(attrs, type) {
  //   attrs.type = name;
  // } // Also errors.

  // function assembleFixed(attrs) {}

  // function assembleEnum(attrs) {}
}

// Helpers.

/**
 * Simple class to split an input string into tokens.
 *
 * There are different types of tokens, characterized by their `id`:
 *
 * + `number` numbers.
 * + `name` references.
 * + `string` double-quoted.
 * + `operator`, anything else, always single character.
 * + `json`, special, must be asked for (the tokenizer doesn't have enough
 *   context to predict these).
 *
 */
function Tokenizer(str) {
  this._str = str;
  this._pos = 0;
  this._queue = new BoundedQueue(3); // Bounded queue of last emitted tokens.
}

Tokenizer.prototype.next = function (opts) {
  var pos = this._pos;
  var str = this._str;
  var c, id;

  if (opts && opts.noSkip) {
    c = str.charAt(this._pos);
  } else {
    while ((c = str.charAt(this._pos)) && /\s/.test(c)) {
      // Skip whitespace.
      this._pos++;
    }
  }

  if (c === '/') {
    // Check if we must skip a comment.
    switch (str.charAt(this._pos + 1)) {
    case '/':
      this._pos += 2;
      while ((c = str.charAt(this._pos)) && c !== '\n') {
        this._pos++;
      }
      break;
    case '*':
      this._pos += 2;
      while ((c = str.charAt(this._pos++))) {
        if (c === '*' && str.charAt(this._pos) === '/') {
          this._pos++;
          break;
        }
      }
      throw this.error('unterminated comment');
    }
  }

  // Save previous position, in case the consumer backtracks. We save both the
  // position before whitespace gets stripped and after to ensure correct
  // behavior even when the options will change on the next call (but still
  // produce good error messages).
  this._queue.push([pos, this._pos]);
  pos = this._pos;

  if (!c) {
    // EOF.
    if (opts && (opts.id || opts.val)) {
      throw this.error('unexpected end of input');
    }
    return null;
  }

  if (opts && opts.id === 'json') {
    id = 'json';
    this._pos = this._endOfJson();
  } else if (c === '"') {
    id = 'string';
    this._pos = this._endOfString();
  } else if (/[0-9]/.test(c)) {
    id = 'number';
    this._pos = this._endOf(/[0-9]/);
  } else if (/[A-Za-z_]/.test(c)) {
    id = 'name';
    this._pos = this._endOf(/[A-Za-z0-9_]/);
  } else {
    id = 'operator';
    this._pos = pos + 1;
  }

  var token = {id: id, val: str.slice(pos, this._pos)};
  if (opts && opts.id && opts.id !== token.id) {
    throw this.error(f('expected %s but got %s', opts.id, id));
  } else if (opts && opts.val && opts.val !== token.val) {
    throw this.error(f('expected %s but got %s', opts.val, token.val));
  } else {
    return token;
  }
};

Tokenizer.prototype.prev = function () {
  var pos = this._queue.pop()[0]; // Before any whitespace.
  if (pos === undefined) {
    throw new Error('cannot backtrack more');
  }
  this._pos = pos;
};

Tokenizer.prototype.error = function (msg) {
  var pos = this._queue.peek()[1]; // Use after whitespace position.
  var str = this._str;
  var lineNum = 1;
  var lineStart = 0;
  var i;
  for (i = 0; i < pos; i++) {
    if (str.charAt(i) === '\n') {
      lineNum++;
      lineStart = i;
    }
  }
  return new Error(f('%s at %d:%d', msg, lineNum, pos - lineStart));
};

/**
 * Generic end of method.
 *
 */
Tokenizer.prototype._endOf = function (pat) {
  var pos = this._pos;
  var str = this._str;
  while (pat.test(str.charAt(pos))) {
    pos++;
  }
  return pos;
};

/**
 * Find end of a string.
 *
 * The specification doesn't explicitly say so, but IDLs likely only allow
 * double quotes for strings (C- and Java-style).
 *
 */
Tokenizer.prototype._endOfString = function () {
  var pos = this._pos + 1; // Skip first double quote.
  var str = this._str;
  var c;
  while ((c = str.charAt(pos))) {
    if (c === '"') {
      return pos + 1;
    }
    if (c === '\\') {
      pos += 2;
    } else {
      pos++;
    }
  }
  this.throwError('unterminated string at ' + this._pos);
};

/**
 * Returns end of JSON object, -1 if the end of the string is reached first.
 *
 * To keep the implementation simple, this function isn't a JSON validator. It
 * will gladly return a result for invalid JSON (which is OK since that will be
 * promptly rejected by the JSON parser). What matters is that it is guaranteed
 * to return the correct end when presented with valid JSON.
 *
 * As a side note, it is unfortunate that this is required. Mixing JSON and
 * custom IDL syntax is not at all elegant (some tokens now have multiple
 * meanings, depending on whether they are part of a default definition or
 * not).
 *
 */
Tokenizer.prototype._endOfJson = function () {
  var pos = this._pos;
  var str = this._str;

  // Handle the case of a single number separately (JSON numbers always start
  // either with `-` or a digit).
  var c = str.charAt(pos++);
  if (/[\d-]/.test(c)) {
    while (/[eE\d.+-]/.test(str.charAt(pos))) {
      pos++;
    }
    return pos;
  }

  // String, object, or array.
  var depth = 0;
  var literal = false;
  do {
    switch (c) {
    case '{':
    case '[':
      if (!literal) { depth++; }
      break;
    case '}':
    case ']':
      if (!literal && !--depth) {
        return pos;
      }
      break;
    case '"':
      literal = !literal;
      if (!depth && !literal) {
        return pos;
      }
      break;
    case '\\':
      pos++; // Skip the next character.
    }
  } while ((c = str.charAt(pos++)));

  throw new Error('invalid JSON at ' + this._pos);
};

/**
 * Simple bounded queue.
 *
 * Not the fastest, but will definitely do.
 *
 */
function BoundedQueue(length) {
  this._length = length | 0;
  this._data = [];
}

BoundedQueue.prototype.push = function (val) {
  this._data.push(val);
  if (this._data.length > this._length) {
    this._data.shift();
  }
};

BoundedQueue.prototype.pop = function () { return this._data.pop(); };

BoundedQueue.prototype.peek = function () {
  return this._data[this._data.length - 1];
};

module.exports = {
  BoundedQueue: BoundedQueue,
  Tokenizer: Tokenizer,
  assemble: assemble,
  load: load
};
<commit_msg>Finish first IDL assembler.<commit_after>/* jshint node: true */

// TODO: backtick escaping for names.
// TODO: Namespacing when importing protocols.

'use strict';

/**
 * Schema parsing and loading utilities.
 *
 */

var fs = require('fs'),
    path = require('path'),
    util = require('util');

var f = util.format;

/**
 * Try to load a schema.
 *
 * This method will attempt to load schemas from a file if the schema passed is
 * a string which isn't valid JSON and contains at least one slash.
 *
 */
function load(schema) {
  var obj;
  if (typeof schema == 'string') {
    try {
      obj = JSON.parse(schema);
    } catch (err) {
      if (~schema.indexOf('/')) {
        // This can't be a valid name, so we interpret is as a filepath. This
        // makes is always feasible to read a file, independent of its name
        // (i.e. even if its name is valid JSON), by prefixing it with `./`.
        obj = JSON.parse(fs.readFileSync(schema));
      }
    }
  }
  if (obj === undefined) {
    obj = schema;
  }
  return obj;
}

/**
 * Assemble an IDL file into a decoded schema.
 *
 * attrs: name, namespace, types, messages, (and any other annotations?)
 *
 */
function assemble(fpath, opts, cb) {
  if (!cb && typeof opts == 'function') {
    cb = opts;
    opts = undefined;
  }

  opts = opts || {};
  opts.imports = opts.imports || {};

  var dpath = path.dirname(fpath);
  var attrs = opts.imports[fpath];
  if (attrs) {
    // Already imported, just skip. We don't do any circularity checks because
    // any such errors will be caught at parsing time.
    cb(null, attrs);
    return;
  }
  attrs = opts.imports[fpath] = {types: [], messages: {}};

  var tk; // Tokenizer.
  fs.readFile(fpath, {encoding: 'utf8'}, function (err, str) {
    if (err) {
      cb(err);
      return;
    }
    tk = new Tokenizer(str);
    assemblePreamble(null, null, cb);
  });

  function assemblePreamble(err, importedAttrs) {
    // Merge imported attributes. We do this first to avoid duplicating error
    // checking (we guard it by a branch instead).
    if (importedAttrs) {
      attrs.types = attrs.types.concat(importedAttrs.types || []);
      Object.keys(importedAttrs.messages).forEach(function (name) {
        if (attrs.messages[name]) {
          err = new Error(f('duplicate message: %s', name));
          return;
        }
        attrs.messages[name] = importedAttrs.messages[name];
      });
    }

    if (err) {
      cb(err);
      return;
    }

    try {
      tk.next();
    } catch (err) {
      // EOF.
      cb(err);
      return;
    }
    if (tk.get().val === 'import') {
      assembleImport();
      return;
    }

    try {
      while (tk.get().val === '@') {
        readAnnotation(attrs);
      }
      readProtocol(attrs);
      tk.assertDone();
    } catch (err) {
      cb(new Error(f('%s when parsing %s', err.stack, fpath)));
    }
    cb(null, attrs);
  }

  function assembleImport() {
    try {
      var importKind = tk.next({id: 'name'}).val;
      var fpath = path.join(dpath, JSON.parse(tk.next({id: 'string'}).val));
      tk.next({val: ';'});
    } catch (err) {
      cb(err);
      return;
    }

    if (importKind === 'idl') {
      assemble(fpath, opts, assemblePreamble);
    } else {
      fs.readFile(fpath, function (err, str) {
        if (err) {
          cb(err);
          return;
        }
        try {
          var attrs = JSON.parse(str);
        } catch (err) {
          cb(err);
          return;
        }
        switch (importKind) {
        case 'protocol':
          assemblePreamble(null, attrs);
          break;
        case 'schema':
          assemblePreamble(null, {types: [attrs]});
          break;
        default:
          assemblePreamble(tk.error('invalid import'));
        }
      });
    }
  }

  function readAnnotation(attrs) {
    tk.get({val: '@'});
    // Annotations are allowed to have names which aren't valid Avro names,
    // we must advance until we hit the first left parenthesis.
    var parts = [];
    while (tk.next().val !== '(') {
      parts.push(tk.get().val);
    }
    // Done parsing the annotation's name.
    attrs[parts.join('')] = JSON.parse(tk.next({id: 'json'}).val);
    tk.next({val: ')'});
    tk.next();
  }

  function readProtocol(attrs) {
    tk.get({val: 'protocol'});
    attrs.protocol = tk.next({id: 'name'}).val;
    tk.next({val: '{'});
    tk.next();
    while (tk.get().val !== '}') {
      var typeAttrs = readType();
      if (typeAttrs.name) {
        // This was a named type declaration.
        attrs.types.push(typeAttrs);
      } else {
        readMessage(attrs, typeAttrs);
      }
    }
  }

  function readMessage(protocolAttrs, responseAttrs) {
    var messageAttrs = {response: responseAttrs};
    while (tk.get().val === '@') {
      readAnnotation(messageAttrs);
    }

    var name = tk.get({id: 'name'}).val;
    if (protocolAttrs.messages[name]) {
      // We have to do this check here otherwise the duplicate will be
      // overwritten (and `parse` won't be able to catch it).
      throw new Error(f('duplicate message: %s', name));
    }

    messageAttrs.request = [];
    tk.next({val: '('});
    if (tk.next().val !== ')') {
      tk.prev();
      do {
        tk.next(); // Skip `(` or `,`.
        messageAttrs.request.push(readField());
      } while (tk.get().val !== ')');
    }
    if (tk.next().val === 'throws') {
      // It doesn't seem like the IDL allows multiple error types, even though
      // the spec always prescribes a union (or they don't indicate which
      // syntax to use). To be safe, we'll only allow one custom error type.
      tk.next();
      messageAttrs.errors = [readType()];
    } else if (tk.get().val === 'oneway') {
      tk.next();
      messageAttrs['one-way'] = true;
    }
    tk.get({val: ';'});
    protocolAttrs.messages[name] = messageAttrs;
    tk.next();
  }

  // Doesn't include the terminator.
  function readField() {
    var attrs = {type: readType()};
    // The spec somehow breaks consistency by allowing the `order` annotation
    // to be placed before the type's name rather than before the field's name.
    // We must manually place it correctly.
    if (attrs.type.order) {
      attrs.order = attrs.type.order;
      delete attrs.type.order;
      if (Object.keys(attrs.type).length === 1) {
        attrs.type = attrs.type.type;
      }
    }
    while (tk.get().val === '@') {
      readAnnotation(attrs);
    }
    attrs.name = tk.get({id: 'name'}).val;
    if (tk.next().val === '=') {
      attrs['default'] = tk.next({id: 'json'});
    }
    return attrs;
  }

  function readType() {
    var attrs = {};
    while (tk.get().val === '@') {
      readAnnotation(attrs);
    }

    switch (tk.get().val) {
    case 'record':
    case 'error':
      return readRecord(attrs);
    case 'fixed':
      return readFixed(attrs);
    case 'enum':
      return readEnum(attrs);
    case 'map':
      return readMap(attrs);
    case 'array':
      return readArray(attrs);
    case 'union':
      if (Object.keys(attrs).length) {
        throw new Error('unions cannot be annotated');
      }
      return readUnion();
    default:
      var type = tk.get().val === 'void' ? 'null' : tk.get().val;
      tk.next();
      if (Object.keys(attrs).length) {
        attrs.type = type;
        return attrs;
      } else {
        return type;
      }
    }
  }

  function readFixed(attrs) {
    attrs.type = tk.get({val: 'fixed'}).val;
    attrs.name = tk.next({id: 'name'}).val;
    tk.next({val: '('});
    attrs.size = parseInt(tk.next({id: 'number'}).val);
    tk.next({val: ')'});
    tk.next({val: ';'});
    tk.next();
    return attrs;
  }

  function readMap(attrs) {
    attrs.type = tk.get({val: 'map'}).val;
    tk.next({val: '<'});
    tk.next();
    attrs.values = readType();
    tk.get({val: '>'});
    tk.next();
    return attrs;
  }

  function readArray(attrs) {
    attrs.type = tk.get({val: 'array'}).val;
    tk.next({val: '<'});
    tk.next();
    attrs.items = readType();
    tk.get({val: '>'});
    tk.next();
    return attrs;
  }

  function readEnum(attrs) {
    attrs.type = tk.get({val: 'enum'}).val;
    attrs.name = tk.next({id: 'name'}).val;
    tk.next({val: '{'});
    attrs.symbols = [];
    if (tk.next().val !== '}') {
      tk.prev();
      do {
        attrs.symbols.push(tk.next().val);
      } while (tk.next().val !== '}');
    }
    tk.next();
    return attrs;
  }

  function readUnion() {
    var attrs = [];
    tk.get({val: 'union'});
    tk.next({val: '{'});
    if (tk.next().val !== '}') {
      tk.prev();
      do {
        tk.next();
        attrs.push(readType());
      } while (tk.get().val !== '}');
    }
    tk.next();
    return attrs;
  }

  function readRecord(attrs) {
    attrs.type = tk.get({id: 'name'}).val;
    attrs.name = tk.next({id: 'name'}).val;
    attrs.fields = [];
    tk.next({val: '{'});
    while (tk.next().val !== '}') {
      attrs.fields.push(readField());
      tk.get({val: ';'});
    }
    tk.next();
    return attrs;
  }
}

// Helpers.

/**
 * Simple class to split an input string into tokens.
 *
 * There are different types of tokens, characterized by their `id`:
 *
 * + `number` numbers.
 * + `name` references.
 * + `string` double-quoted.
 * + `operator`, anything else, always single character.
 * + `json`, special, must be asked for (the tokenizer doesn't have enough
 *   context to predict these).
 *
 */
function Tokenizer(str) {
  this._str = str;
  this._pos = 0;
  this._queue = new BoundedQueue(3); // Bounded queue of last emitted tokens.
  this._token = undefined; // Current token.
}

Tokenizer.prototype.get = function (opts) {
  if (opts && opts.id && opts.id !== this._token.id) {
    throw this.error(f('expected %s but got %s', opts.id, this._token.id));
  } else if (opts && opts.val && opts.val !== this._token.val) {
    throw this.error(f('expected %s but got %s', opts.val, this._token.val));
  } else {
    return this._token;
  }
};

Tokenizer.prototype.next = function (opts) {
  this._skip();
  this._queue.push(this._pos);
  var pos = this._pos;
  var str = this._str;
  var c = str.charAt(pos);
  var id;

  if (!c) {
    throw this.error('unexpected end of input');
  }

  if (opts && opts.id === 'json') {
    id = 'json';
    this._pos = this._endOfJson();
  } else if (c === '"') {
    id = 'string';
    this._pos = this._endOfString();
  } else if (/[0-9]/.test(c)) {
    id = 'number';
    this._pos = this._endOf(/[0-9]/);
  } else if (/[A-Za-z_]/.test(c)) {
    id = 'name';
    this._pos = this._endOf(/[A-Za-z0-9_]/);
  } else {
    id = 'operator';
    this._pos = pos + 1;
  }

  this._token = {id: id, val: str.slice(pos, this._pos)};
  return this.get(opts);
};

Tokenizer.prototype.prev = function () {
  var pos = this._queue.pop();
  if (pos === undefined) {
    throw new Error('cannot backtrack more');
  }
  this._pos = pos;
};

Tokenizer.prototype.error = function (msg) {
  var pos = this._queue.peek() || 1; // Use after whitespace position.
  var str = this._str;
  var lineNum = 1;
  var lineStart = 0;
  var i;
  for (i = 0; i < pos; i++) {
    if (str.charAt(i) === '\n') {
      lineNum++;
      lineStart = i;
    }
  }
  return new Error(f('%s at %d:%d', msg, lineNum, pos - lineStart + 1));
};

Tokenizer.prototype.assertDone = function () {
  this._skip();
  if (this._pos < this._str.length) {
    throw new Error('trailing data');
  }
};

/**
 * Skip whitespace and comments.
 *
 */
Tokenizer.prototype._skip = function () {
  var str = this._str;
  var c;

  while ((c = str.charAt(this._pos)) && /\s/.test(c)) {
    this._pos++;
  }
  if (c === '/') {
    switch (str.charAt(this._pos + 1)) {
    case '/':
      this._pos += 2;
      while ((c = str.charAt(this._pos)) && c !== '\n') {
        this._pos++;
      }
      return this._skip();
    case '*':
      this._pos += 2;
      while ((c = str.charAt(this._pos++))) {
        if (c === '*' && str.charAt(this._pos) === '/') {
          this._pos++;
          return this._skip();
        }
      }
      throw this.error('unterminated comment');
    }
  }
};

/**
 * Generic end of method.
 *
 */
Tokenizer.prototype._endOf = function (pat) {
  var pos = this._pos;
  var str = this._str;
  while (pat.test(str.charAt(pos))) {
    pos++;
  }
  return pos;
};

/**
 * Find end of a string.
 *
 * The specification doesn't explicitly say so, but IDLs likely only allow
 * double quotes for strings (C- and Java-style).
 *
 */
Tokenizer.prototype._endOfString = function () {
  var pos = this._pos + 1; // Skip first double quote.
  var str = this._str;
  var c;
  while ((c = str.charAt(pos))) {
    if (c === '"') {
      return pos + 1;
    }
    if (c === '\\') {
      pos += 2;
    } else {
      pos++;
    }
  }
  this.throwError('unterminated string at ' + this._pos);
};

/**
 * Returns end of JSON object, -1 if the end of the string is reached first.
 *
 * To keep the implementation simple, this function isn't a JSON validator. It
 * will gladly return a result for invalid JSON (which is OK since that will be
 * promptly rejected by the JSON parser). What matters is that it is guaranteed
 * to return the correct end when presented with valid JSON.
 *
 * As a side note, it is unfortunate that this is required. Mixing JSON and
 * custom IDL syntax is not at all elegant (some tokens now have multiple
 * meanings, depending on whether they are part of a default definition or
 * not).
 *
 */
Tokenizer.prototype._endOfJson = function () {
  var pos = this._pos;
  var str = this._str;

  // Handle the case of a single number separately (JSON numbers always start
  // either with `-` or a digit).
  var c = str.charAt(pos++);
  if (/[\d-]/.test(c)) {
    while (/[eE\d.+-]/.test(str.charAt(pos))) {
      pos++;
    }
    return pos;
  }

  // String, object, or array.
  var depth = 0;
  var literal = false;
  do {
    switch (c) {
    case '{':
    case '[':
      if (!literal) { depth++; }
      break;
    case '}':
    case ']':
      if (!literal && !--depth) {
        return pos;
      }
      break;
    case '"':
      literal = !literal;
      if (!depth && !literal) {
        return pos;
      }
      break;
    case '\\':
      pos++; // Skip the next character.
    }
  } while ((c = str.charAt(pos++)));

  throw new Error('invalid JSON at ' + this._pos);
};

/**
 * Simple bounded queue.
 *
 * Not the fastest, but will definitely do.
 *
 */
function BoundedQueue(length) {
  this._length = length | 0;
  this._data = [];
}

BoundedQueue.prototype.push = function (val) {
  this._data.push(val);
  if (this._data.length > this._length) {
    this._data.shift();
  }
};

BoundedQueue.prototype.peek = function () {
  return this._data[this._data.length - 1];
};

BoundedQueue.prototype.pop = function () { return this._data.pop(); };

module.exports = {
  BoundedQueue: BoundedQueue,
  Tokenizer: Tokenizer,
  assemble: assemble,
  load: load
};
<|endoftext|>


<commit_before>var noble = require('noble');
var events = require('events');
var util = require('util');
var JuggglowMap = require('./utils/map');
var serviceFn = require('./service/index');

/**
 * Represents a juggglow SDK service.
 * With a service you can scan and connect juggglows and execute commands
 * @constructor
*/
function Service(n) {
    events.EventEmitter.call(this);

    // PUBLIC
    this.name = n;
    this.connectedProps = new JuggglowMap('CONNECTED PROPS');
    this.foundProps = new JuggglowMap('FOUND PROPS');

    // PRIVATE
    this._bluetooth = noble;
}

util.inherits(Service, events.EventEmitter);

/**
Scan for Juggglow devices for given seconds

@param {Object} options
    seconds: Number of seconds (default: 5)
@public
*/
Service.prototype.scan = function(options) {
  options = options || {};

  return serviceFn.scan({
    seconds: options.seconds,
    foundProps: this.foundProps,
    bluetooth: this._bluetooth,
    service: this
  });
};

/**
Connect to given Juggglow device

@param {Object} options
    * {Object} peripheral - Juggglow (noble peripheral) returned from connect method
@public
*/
Service.prototype.connect = function(options) {
  options = options || {};

  return serviceFn.connect({
    peripheral: options.peripheral,
    connectedProps: this.connectedProps,
    bluetooth: this._bluetooth,
    service: this
  });
};

/**
Connect all found Juggglow devices

@param {Object} options
    * {number} [count] - Amount of found device to connect
    * {boolean} [race] - Return result after first connection (true) OR wait until all are connected
@public
*/
Service.prototype.connectAll = function(options) {
  options = options || {};

  return serviceFn.connectAll({
    count: options.count,
    race: options.race,
    foundProps: this.foundProps,
    connectedProps: this.connectedProps,
    bluetooth: this._bluetooth,
    service: this
  });
};

/**
Shutdown all connected Juggglow devices

@public
*/
Service.prototype.shutdownAll = function() {
  serviceFn.shutdownAll({
    props: this.connectedProps
  });
};

/**
Enable or disable standalone mode

@param {Object} options
    * {boolean} enable
@public
*/
Service.prototype.standAloneModeAll = function(options) {
  options = options || {};

  serviceFn.standAloneModeAll({
    enable: options.enable,
    props: this.connectedProps
  });
};

/**
Set duration of Stop Mode for all connected Juggglow devices

@param {Object} options
    * {number} seconds - Duration in seconds [60-65535] (default: 60)
@public
*/
Service.prototype.stopModeAll = function(options) {
  options = options || {};

  serviceFn.stopModeAll({
    seconds: options.seconds,
    props: this.connectedProps
  });
};

/**
Set duration of Standby Mode for all connected Juggglow devices

@param {Object} options
    * {number} minutes - Duration in minutes [1-65535] (default: 30)
@public
*/
Service.prototype.standbyModeAll = function(options) {
  options = options || {};

  serviceFn.standbyModeAll({
    minutes: options.minutes,
    props: this.connectedProps
  });
};

/**
Enable or disable the charging indication for all Juggglow devices

@param {Object} options
    * {boolean} enable
@public
*/
Service.prototype.chargingIndicationAll = function(options) {
  options = options || {};

  serviceFn.chargingIndicationAll({
    enable: options.enable,
    props: this.connectedProps
  });
};

/**
Set the tx power (signal strength) of the Juggglow bluetooth module

@param {Object} options
    * {number} strength - Strength [0-14] (default: 10)
@public
*/
Service.prototype.txPowerAll = function(options) {
  options = options || {};

  serviceFn.txPowerAll({
    strength: options.strength,
    props: this.connectedProps
  });
};

/**
Change the color of all connected Juggglow devices

@param {Object} options
    * {array} color - color for top LED (RGB color array [255,0,0] for red)
    * {array} [color2] - color for bottom LED
@public
*/
Service.prototype.colorAll = function(options) {
  options = Array.isArray(options) ? { color: options} : options;
  options = options || {};

  serviceFn.colorAll({
    color: options.color,
    color2: options.color2,
    props: this.connectedProps
  });
};

/**
Fade between two colors for all connected Juggglow devices

@param {Object} options
    * {array} colorStart - start color (RGB color array [255,0,0] for red)
    * {array} colorEnd - end color
    * {number} duration - duration of fade in hsec
    * {String} mode - define fading mode (e.g. '03' for both halfs fading, cf. Juggglow API from www.juggglow.com)
*/
Service.prototype.fadeAll = function(options) {
  options = options || {};

  serviceFn.fadeAll({
    colorStart: options.colorStart,
    colorEnd: options.colorEnd,
    duration: options.duration,
    mode: options.mode,
    props: this.connectedProps
  });
};

/**
Rainbow effect for all connected Juggglow devices

@param {Object} options
    * {number} duration - duration of rainbow effect in hsec
    * {String} mode - define rainbow mode (e.g. '03' for both halfs fading, cf. Juggglow API from www.juggglow.com)
*/
Service.prototype.rainbowAll = function(options) {
  options = options || {};

  serviceFn.rainbowAll({
    duration: options.duration,
    mode: options.mode,
    props: this.connectedProps
  });
};

/**
Strobe all connected Juggglow devices

@param {Object} options
    * {number} interval - interval in ms [10-2560] (eg. 2560 for 2,56 seconds)
    * {number} dutyCycle - duty cycle of ON phase of effect [0-1] (e.g. 1 for 100% ON, 0% OFF)
    * {array} color - color for top LED (RGB color array [255,0,0] for red)
    * {array} [color2] - color for bottom LED
*/
Service.prototype.strobeAll = function(options) {
  options = options || {};

  serviceFn.strobeAll({
    interval: options.interval,
    dutyCycle: options.dutyCycle,
    color: options.color,
    color2: options.color2,
    props: this.connectedProps
  });
};

/**
Random color for all connected Juggglow devices

@param {Object} options
    * {number} interval - interval of one color in ms [100-25600] (eg. 25600 for 25,6 seconds)
    * {number} dutyCycle - duty cycle of ON phase of effect [0-1] (e.g. 1 for 100% ON, 0% OFF)
    * {String} mode - define random color mode (e.g. '03' for both halfs same color, cf. Juggglow API from www.juggglow.com)
@public
*/
Service.prototype.randomColorAll = function(options) {
  options = options || {};

  serviceFn.randomColorAll({
    interval: options.interval,
    dutyCycle: options.dutyCycle,
    mode: options.mode,
    props: this.connectedProps
  });
};

/**
Start a sequence for all Juggglow devices

@param {Object} options
    * {number} sequenceNo - Start sequence with this number
    * {number} delay - Delay start for number of seconds
@public
*/
Service.prototype.runSequenceAll = function(options) {
  options = options || {};

  serviceFn.runSequenceAll({
    sequenceNo: options.sequenceNo,
    delay: options.delay,
    props: this.connectedProps
  });
};

/**
Set a effect on event for all Juggglow devices

@param {Object} options
    * {String} event - Event (e.g. 'boot', 'powerloss', 'catch', 'throw', 'drop')
    * {String} command - Hex command to configure (eg. '34FFFFFFFFFFFF' for white colored ball; cf. Juggglow API from www.juggglow.com)
    * {boolean} permanent - Make it permanent (persist after reboot of Juggglow device)
@public
*/
Service.prototype.effectOnEventAll = function(options) {
  options = options || {};

  serviceFn.effectOnEventAll({
    event: options.event,
    command: options.command,
    permanent: options.permanent,
    props: this.connectedProps
  });
};

/**
Remove all effects on events for all Juggglow devices

@public
*/
Service.prototype.removeEffectOnEventsAll = function() {
  serviceFn.removeEffectOnEventsAll({
    props: this.connectedProps
  });
};

/**
Set a raw hex command for all Juggglow devices
The available commands are listed in the Juggglow API from www.juggglow.com

An example for a raw hex command:
'34FFFFFFFFFFFF' will set the ball color to white

@param {Object} options
    * {String} command - hex command
@public
*/
Service.prototype.rawAll = function(options) {
  options = options || {};

  serviceFn.rawAll({
    command: options.command,
    props: this.connectedProps
  });
};

module.exports = Service;<commit_msg>Fix mistake in jsdoc comment<commit_after>var noble = require('noble');
var events = require('events');
var util = require('util');
var JuggglowMap = require('./utils/map');
var serviceFn = require('./service/index');

/**
 * Represents a juggglow SDK service.
 * With a service you can scan and connect juggglows and execute commands
 * @constructor
*/
function Service(n) {
    events.EventEmitter.call(this);

    // PUBLIC
    this.name = n;
    this.connectedProps = new JuggglowMap('CONNECTED PROPS');
    this.foundProps = new JuggglowMap('FOUND PROPS');

    // PRIVATE
    this._bluetooth = noble;
}

util.inherits(Service, events.EventEmitter);

/**
Scan for Juggglow devices for given seconds

@param {Object} options
    seconds: Number of seconds (default: 5)
@public
*/
Service.prototype.scan = function(options) {
  options = options || {};

  return serviceFn.scan({
    seconds: options.seconds,
    foundProps: this.foundProps,
    bluetooth: this._bluetooth,
    service: this
  });
};

/**
Connect to given Juggglow device

@param {Object} options
    * {Object} peripheral - Juggglow (noble peripheral) returned from scan method
@public
*/
Service.prototype.connect = function(options) {
  options = options || {};

  return serviceFn.connect({
    peripheral: options.peripheral,
    connectedProps: this.connectedProps,
    bluetooth: this._bluetooth,
    service: this
  });
};

/**
Connect all found Juggglow devices

@param {Object} options
    * {number} [count] - Amount of found device to connect
    * {boolean} [race] - Return result after first connection (true) OR wait until all are connected
@public
*/
Service.prototype.connectAll = function(options) {
  options = options || {};

  return serviceFn.connectAll({
    count: options.count,
    race: options.race,
    foundProps: this.foundProps,
    connectedProps: this.connectedProps,
    bluetooth: this._bluetooth,
    service: this
  });
};

/**
Shutdown all connected Juggglow devices

@public
*/
Service.prototype.shutdownAll = function() {
  serviceFn.shutdownAll({
    props: this.connectedProps
  });
};

/**
Enable or disable standalone mode

@param {Object} options
    * {boolean} enable
@public
*/
Service.prototype.standAloneModeAll = function(options) {
  options = options || {};

  serviceFn.standAloneModeAll({
    enable: options.enable,
    props: this.connectedProps
  });
};

/**
Set duration of Stop Mode for all connected Juggglow devices

@param {Object} options
    * {number} seconds - Duration in seconds [60-65535] (default: 60)
@public
*/
Service.prototype.stopModeAll = function(options) {
  options = options || {};

  serviceFn.stopModeAll({
    seconds: options.seconds,
    props: this.connectedProps
  });
};

/**
Set duration of Standby Mode for all connected Juggglow devices

@param {Object} options
    * {number} minutes - Duration in minutes [1-65535] (default: 30)
@public
*/
Service.prototype.standbyModeAll = function(options) {
  options = options || {};

  serviceFn.standbyModeAll({
    minutes: options.minutes,
    props: this.connectedProps
  });
};

/**
Enable or disable the charging indication for all Juggglow devices

@param {Object} options
    * {boolean} enable
@public
*/
Service.prototype.chargingIndicationAll = function(options) {
  options = options || {};

  serviceFn.chargingIndicationAll({
    enable: options.enable,
    props: this.connectedProps
  });
};

/**
Set the tx power (signal strength) of the Juggglow bluetooth module

@param {Object} options
    * {number} strength - Strength [0-14] (default: 10)
@public
*/
Service.prototype.txPowerAll = function(options) {
  options = options || {};

  serviceFn.txPowerAll({
    strength: options.strength,
    props: this.connectedProps
  });
};

/**
Change the color of all connected Juggglow devices

@param {Object} options
    * {array} color - color for top LED (RGB color array [255,0,0] for red)
    * {array} [color2] - color for bottom LED
@public
*/
Service.prototype.colorAll = function(options) {
  options = Array.isArray(options) ? { color: options} : options;
  options = options || {};

  serviceFn.colorAll({
    color: options.color,
    color2: options.color2,
    props: this.connectedProps
  });
};

/**
Fade between two colors for all connected Juggglow devices

@param {Object} options
    * {array} colorStart - start color (RGB color array [255,0,0] for red)
    * {array} colorEnd - end color
    * {number} duration - duration of fade in hsec
    * {String} mode - define fading mode (e.g. '03' for both halfs fading, cf. Juggglow API from www.juggglow.com)
*/
Service.prototype.fadeAll = function(options) {
  options = options || {};

  serviceFn.fadeAll({
    colorStart: options.colorStart,
    colorEnd: options.colorEnd,
    duration: options.duration,
    mode: options.mode,
    props: this.connectedProps
  });
};

/**
Rainbow effect for all connected Juggglow devices

@param {Object} options
    * {number} duration - duration of rainbow effect in hsec
    * {String} mode - define rainbow mode (e.g. '03' for both halfs fading, cf. Juggglow API from www.juggglow.com)
*/
Service.prototype.rainbowAll = function(options) {
  options = options || {};

  serviceFn.rainbowAll({
    duration: options.duration,
    mode: options.mode,
    props: this.connectedProps
  });
};

/**
Strobe all connected Juggglow devices

@param {Object} options
    * {number} interval - interval in ms [10-2560] (eg. 2560 for 2,56 seconds)
    * {number} dutyCycle - duty cycle of ON phase of effect [0-1] (e.g. 1 for 100% ON, 0% OFF)
    * {array} color - color for top LED (RGB color array [255,0,0] for red)
    * {array} [color2] - color for bottom LED
*/
Service.prototype.strobeAll = function(options) {
  options = options || {};

  serviceFn.strobeAll({
    interval: options.interval,
    dutyCycle: options.dutyCycle,
    color: options.color,
    color2: options.color2,
    props: this.connectedProps
  });
};

/**
Random color for all connected Juggglow devices

@param {Object} options
    * {number} interval - interval of one color in ms [100-25600] (eg. 25600 for 25,6 seconds)
    * {number} dutyCycle - duty cycle of ON phase of effect [0-1] (e.g. 1 for 100% ON, 0% OFF)
    * {String} mode - define random color mode (e.g. '03' for both halfs same color, cf. Juggglow API from www.juggglow.com)
@public
*/
Service.prototype.randomColorAll = function(options) {
  options = options || {};

  serviceFn.randomColorAll({
    interval: options.interval,
    dutyCycle: options.dutyCycle,
    mode: options.mode,
    props: this.connectedProps
  });
};

/**
Start a sequence for all Juggglow devices

@param {Object} options
    * {number} sequenceNo - Start sequence with this number
    * {number} delay - Delay start for number of seconds
@public
*/
Service.prototype.runSequenceAll = function(options) {
  options = options || {};

  serviceFn.runSequenceAll({
    sequenceNo: options.sequenceNo,
    delay: options.delay,
    props: this.connectedProps
  });
};

/**
Set a effect on event for all Juggglow devices

@param {Object} options
    * {String} event - Event (e.g. 'boot', 'powerloss', 'catch', 'throw', 'drop')
    * {String} command - Hex command to configure (eg. '34FFFFFFFFFFFF' for white colored ball; cf. Juggglow API from www.juggglow.com)
    * {boolean} permanent - Make it permanent (persist after reboot of Juggglow device)
@public
*/
Service.prototype.effectOnEventAll = function(options) {
  options = options || {};

  serviceFn.effectOnEventAll({
    event: options.event,
    command: options.command,
    permanent: options.permanent,
    props: this.connectedProps
  });
};

/**
Remove all effects on events for all Juggglow devices

@public
*/
Service.prototype.removeEffectOnEventsAll = function() {
  serviceFn.removeEffectOnEventsAll({
    props: this.connectedProps
  });
};

/**
Set a raw hex command for all Juggglow devices
The available commands are listed in the Juggglow API from www.juggglow.com

An example for a raw hex command:
'34FFFFFFFFFFFF' will set the ball color to white

@param {Object} options
    * {String} command - hex command
@public
*/
Service.prototype.rawAll = function(options) {
  options = options || {};

  serviceFn.rawAll({
    command: options.command,
    props: this.connectedProps
  });
};

module.exports = Service;<|endoftext|>


<commit_before>var keystone = require('../'),
	utils = require('keystone-utils');

/**
 * Signs in a user user matching the lookup filters
 *
 * @param {Object} lookup - must contain email and password
 * @param {Object} req - express request object
 * @param {Object} res - express response object
 * @param {function()} onSuccess callback, is passed the User instance
 * @param {function()} onFail callback
 */

var signin = exports.signin = function(lookup, req, res, onSuccess, onFail) {

	var User = keystone.list(keystone.get('user model'));

	var doSignin = function(user) {
		req.session.regenerate(function() {

			req.user = user;
			req.session.userId = user.id;

			// secure the user ID cookie with half the password hash, so if the password changes
			// the cookie will no longer be valid

			var userToken = user.id + ':' + user.password.substr(0, Math.round(user.password.length / 2));
			res.cookie('keystone.uid', userToken, { signed: true, httpOnly: true });

			onSuccess(user);

		});
	};

	if (utils.isObject(lookup)) {

		// match email address and password

		User.model.findOne({ email: lookup.email }).exec(function(err, user) {
			if (user) {
				user._.password.compare(lookup.password, function(err, isMatch) {
					if (!err && isMatch) {
						doSignin(user);
					}
					else {
						onFail(err);
					}
				});
			} else {
				onFail(err);
			}
		});

	} else {

		// match the userId, with optional password check

		var userId = lookup.substr(0, lookup.indexOf(':')),
			passwordCheck = lookup.substr(lookup.indexOf(':') + 1);

		User.model.findById(userId).exec(function(err, user) {
			if (user && (passwordCheck === user.password.substr(0, passwordCheck.length) || !passwordCheck)) {
				doSignin(user);
			} else {
				onFail(err);
			}
		});
	}

};

/**
 * Signs the current user out and resets the session
 *
 * @param {Object} req - express request object
 * @param {Object} res - express response object
 * @param {function()} next callback
 */

var signout = exports.signout = function(req, res, next) {

	res.clearCookie('keystone.uid');
	req.user = null;

	req.session.regenerate(next);

};

/**
 * Middleware to ensure session persistence across server restarts
 *
 * Looks for a userId cookie, and if present, and there is no user signed in,
 * automatically signs the user in.
 *
 * @param {Object} req - express request object
 * @param {Object} res - express response object
 * @param {function()} next callback
 */

exports.persist = function(req, res, next) {

	var User = keystone.list(keystone.get('user model'));

	if (!req.session.userId && req.signedCookies['keystone.uid']) {

		var _next = function() { next(); }; // otherwise the matching user is passed to next() which messes with the middleware signature
		signin(req.signedCookies['keystone.uid'], req, res, _next, _next);

	} else if (req.session.userId) {

		User.model.findById(req.session.userId).exec(function(err, user) {

			if (err)
				return next(err);

			req.user = user;
			next();

		});

	}
	else {
		next();
	}

};

/**
 * Middleware to enable access to Keystone
 *
 * Bounces the user to the signin screen if they are not signed in or do not have permission.
 *
 * @param {Object} req - express request object
 * @param {Object} res - express response object
 * @param {function()} next callback
 */

exports.keystoneAuth = function(req, res, next) {

	if (!req.user || !req.user.canAccessKeystone) {
		var from = new RegExp('^\/keystone\/?$', 'i').test(req.url) ? '' : '?from=' + req.url;
		return res.redirect(keystone.get('signin url') + from);
	}

	next();

};
<commit_msg>Fixing an issue signing users in by id<commit_after>var keystone = require('../'),
	utils = require('keystone-utils');

/**
 * Signs in a user user matching the lookup filters
 *
 * @param {Object} lookup - must contain email and password
 * @param {Object} req - express request object
 * @param {Object} res - express response object
 * @param {function()} onSuccess callback, is passed the User instance
 * @param {function()} onFail callback
 */

var signin = exports.signin = function(lookup, req, res, onSuccess, onFail) {

	var User = keystone.list(keystone.get('user model'));

	var doSignin = function(user) {
		req.session.regenerate(function() {

			req.user = user;
			req.session.userId = user.id;

			// secure the user ID cookie with half the password hash, so if the password changes
			// the cookie will no longer be valid

			var userToken = user.id + ':' + user.password.substr(0, Math.round(user.password.length / 2));
			res.cookie('keystone.uid', userToken, { signed: true, httpOnly: true });

			onSuccess(user);

		});
	};

	if (utils.isObject(lookup)) {

		// match email address and password
		User.model.findOne({ email: lookup.email }).exec(function(err, user) {
			if (user) {
				user._.password.compare(lookup.password, function(err, isMatch) {
					if (!err && isMatch) {
						doSignin(user);
					}
					else {
						onFail(err);
					}
				});
			} else {
				onFail(err);
			}
		});

	} else {

		// match the userId, with optional password check
		var userId = (lookup.indexOf(':') > 0) ? lookup.substr(0, lookup.indexOf(':')) : lookup,
			passwordCheck = lookup.substr(lookup.indexOf(':') + 1);

		User.model.findById(userId).exec(function(err, user) {
			if (user && (!passwordCheck || passwordCheck === user.password.substr(0, passwordCheck.length))) {
				doSignin(user);
			} else {
				onFail(err);
			}
		});
	}

};

/**
 * Signs the current user out and resets the session
 *
 * @param {Object} req - express request object
 * @param {Object} res - express response object
 * @param {function()} next callback
 */

var signout = exports.signout = function(req, res, next) {

	res.clearCookie('keystone.uid');
	req.user = null;

	req.session.regenerate(next);

};

/**
 * Middleware to ensure session persistence across server restarts
 *
 * Looks for a userId cookie, and if present, and there is no user signed in,
 * automatically signs the user in.
 *
 * @param {Object} req - express request object
 * @param {Object} res - express response object
 * @param {function()} next callback
 */

exports.persist = function(req, res, next) {

	var User = keystone.list(keystone.get('user model'));

	if (!req.session.userId && req.signedCookies['keystone.uid'] && req.signedCookies['keystone.uid'].indexOf(':') > 0) {

		var _next = function() { next(); }; // otherwise the matching user is passed to next() which messes with the middleware signature
		signin(req.signedCookies['keystone.uid'], req, res, _next, _next);

	} else if (req.session.userId) {

		User.model.findById(req.session.userId).exec(function(err, user) {

			if (err)
				return next(err);

			req.user = user;
			next();

		});

	}
	else {
		next();
	}

};

/**
 * Middleware to enable access to Keystone
 *
 * Bounces the user to the signin screen if they are not signed in or do not have permission.
 *
 * @param {Object} req - express request object
 * @param {Object} res - express response object
 * @param {function()} next callback
 */

exports.keystoneAuth = function(req, res, next) {

	if (!req.user || !req.user.canAccessKeystone) {
		var from = new RegExp('^\/keystone\/?$', 'i').test(req.url) ? '' : '?from=' + req.url;
		return res.redirect(keystone.get('signin url') + from);
	}

	next();

};
<|endoftext|>


<commit_before>/**
 *  Copyright (c) 2010 Marat Nepomnyashy    maratbn@gmail
 *  All rights reserved.
 *
 *  Module:         dojo_dcodeview/DCodeView.js
 *
 *  Description:    A Dojo-based widget for embedding arbitrary source code
 *                  views inside web pages.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *      * Redistributions of source code must retain the above copyright
 *        notice, this list of conditions and the following disclaimer.
 *      * Redistributions in binary form must reproduce the above copyright
 *        notice, this list of conditions and the following disclaimer in the
 *        documentation and/or other materials provided with the distribution.
 *      * Neither the name of the <organization> nor the
 *        names of its contributors may be used to endorse or promote products
 *        derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 *  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

dojo.provide('dcodeview.DCodeView');

dojo.require('dijit._Templated');
dojo.require('dijit._Widget');

dojo.declare(
    'dcodeview.DCodeView',
    [dijit._Widget, dijit._Templated],
    {
        font_size:          "",

        templateString:     [   "
",
                                    "
",
                                        "",
                                            "

",
                                        "	",
                                        "",
                                            "
",
                                                "
",
                                                "
",
                                            "
",
                                            "

",
                                        "	",
                                    "
",
                                "
"
                            ].join(""),

        postCreate:         function() {
                                this.inherited(arguments);

                                if (this.font_size) {
                                    dojo.style(this.domNode, 'fontSize', this.font_size);
                                }

                                var strCode = this.srcNodeRef.innerHTML || "";

                                // This prevents rendering of HTML tags that can mess up appearance of the source.
                                // The user should not be including unescaped HTML in the source anyway because that would
                                // prevent normal viewing without this widget, but just in case.
                                strCode = strCode.replace(/</g,'<').replace(/>/g,'>');

                                // This will produce a consistent array on both FF and IE with a line separator at each odd index.
                                var arrLinesCode = strCode.split(/($)/m);

                                // This will get rid of the odd indices as well as line separators at the start of each even-endex line.
                                var arrLinesCodeUse = [];
                                for (var i = 0; i < arrLinesCode.length; i+=2) {
                                    arrLinesCodeUse.push(arrLinesCode[i].replace(/\r\n|\r|\n/, ""));
                                }

                                // This removes the trailing blank lines from the source code.
                                while (arrLinesCodeUse.length > 0 && !arrLinesCodeUse[arrLinesCodeUse.length -1]) arrLinesCodeUse.pop();

                                var lengthLN = ("" + arrLinesCodeUse.length).length + 1;

                                var arrOutputLines = [], arrOutputCode = [], arrOutputStripes = [];
                                for (var i = 0; i < arrLinesCodeUse.length; i++) {
                                    var strLineNumber = "" + (i + 1);
                                    while (strLineNumber.length < lengthLN) strLineNumber = ' ' + strLineNumber;

                                    arrOutputLines.push(strLineNumber);
                                    arrOutputLines.push("
");

                                    arrOutputCode.push(arrLinesCodeUse[i]);
                                    arrOutputCode.push("
");

                                    arrOutputStripes.push(  "
",
                                                                " ",
                                                            "
");
                                }

                                this._preLineNumbers.innerHTML = arrOutputLines.join("");
                                this._preCode.innerHTML = arrOutputCode.join("");
                                this._divStripes.innerHTML = arrOutputStripes.join("");
                            }
    });<commit_msg>Went back to using s for each line, but with '\r\n' instead of
 for consistent output and clipboard copy on both Firefox & IE8.<commit_after>/**
 *  Copyright (c) 2010 Marat Nepomnyashy    maratbn@gmail
 *  All rights reserved.
 *
 *  Module:         dojo_dcodeview/DCodeView.js
 *
 *  Description:    A Dojo-based widget for embedding arbitrary source code
 *                  views inside web pages.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *      * Redistributions of source code must retain the above copyright
 *        notice, this list of conditions and the following disclaimer.
 *      * Redistributions in binary form must reproduce the above copyright
 *        notice, this list of conditions and the following disclaimer in the
 *        documentation and/or other materials provided with the distribution.
 *      * Neither the name of the <organization> nor the
 *        names of its contributors may be used to endorse or promote products
 *        derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 *  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

dojo.provide('dcodeview.DCodeView');

dojo.require('dijit._Templated');
dojo.require('dijit._Widget');

dojo.declare(
    'dcodeview.DCodeView',
    [dijit._Widget, dijit._Templated],
    {
        font_size:          "",

        templateString:     [   "
",
                                    "
",
                                        "",
                                            "

",
                                        "	",
                                        "",
                                            "
",
                                                "
",
                                                "
",
                                            "
",
                                            "

",
                                        "	",
                                    "
",
                                "
"
                            ].join(""),

        postCreate:         function() {
                                this.inherited(arguments);

                                if (this.font_size) {
                                    dojo.style(this.domNode, 'fontSize', this.font_size);
                                }

                                var strCode = this.srcNodeRef.innerHTML || "";

                                // This prevents rendering of HTML tags that can mess up appearance of the source.
                                // The user should not be including unescaped HTML in the source anyway because that would
                                // prevent normal viewing without this widget, but just in case.
                                strCode = strCode.replace(/</g,'<').replace(/>/g,'>');

                                // This will produce a consistent array on both FF and IE with a line separator at each odd index.
                                var arrLinesCode = strCode.split(/($)/m);

                                // This will get rid of the odd indices as well as line separators at the start of each even-endex line.
                                var arrLinesCodeUse = [];
                                for (var i = 0; i < arrLinesCode.length; i+=2) {
                                    arrLinesCodeUse.push(arrLinesCode[i].replace(/\r\n|\r|\n/, ""));
                                }

                                // This removes the trailing blank lines from the source code.
                                while (arrLinesCodeUse.length > 0 && !arrLinesCodeUse[arrLinesCodeUse.length -1]) arrLinesCodeUse.pop();

                                var lengthLN = ("" + arrLinesCodeUse.length).length + 1;

                                var arrOutputLines = [], arrOutputCode = [], arrOutputStripes = [];
                                for (var i = 0; i < arrLinesCodeUse.length; i++) {
                                    var strLineNumber = "" + (i + 1);
                                    while (strLineNumber.length < lengthLN) strLineNumber = ' ' + strLineNumber;

                                    arrOutputLines.push(    "",
                                                                strLineNumber,
                                                                "\r\n",
                                                            "");

                                    arrOutputCode.push(     "",
                                                                arrLinesCodeUse[i],
                                                                "\r\n",
                                                            "");

                                    arrOutputStripes.push(  "
",
                                                                " ",
                                                            "
");
                                }

                                this._preLineNumbers.innerHTML = arrOutputLines.join("");
                                this._preCode.innerHTML = arrOutputCode.join("");
                                this._divStripes.innerHTML = arrOutputStripes.join("");
                            }
    });<|endoftext|>