<commit_before>/**
 * @fileOverview Wrapper for node-rdkafka Consumer Ctor, a mixin.
 */
var Transform = require('stream').Transform;

var Promise = require('bluebird');
var cip = require('cip');
var kafka = require('node-rdkafka');

var magicByte = require('./magic-byte');

/**
 * Wrapper for node-rdkafka Consumer Ctor, a mixin.
 *
 * @constructor
 */
var Consumer = module.exports = cip.extend();

/**
 * The wrapper of the node-rdkafka package Consumer Ctor.
 *
 * @param {Object} opts Consumer general options.
 * @see https://github.com/edenhill/librdkafka/blob/2213fb29f98a7a73f22da21ef85e0783f6fd67c4/CONFIGURATION.md
 * @return {Promise(kafka.Consumer)} A Promise with the consumer.
 */
Consumer.prototype.getConsumer = Promise.method(function (opts) {
  if (!opts['metadata.broker.list']) {
    opts['metadata.broker.list'] = this.kafkaBrokerUrl;
  }

  console.log('KafkaAvro :: Starting Consumer with opts:', opts);

  var consumer = new kafka.KafkaConsumer(opts);

  consumer.on('event.log', function(log) {
    console.log('node-rdkafka log:', log);
  });

  // hack node-rdkafka
  consumer.__kafkaAvro_getReadStream = consumer.getReadStream;
  consumer.getReadStream = this._getReadStreamWrapper.bind(this, consumer);

  consumer.__kafkaAvro_on = consumer.on;
  consumer.on = this._onWrapper.bind(this, consumer);

  return new Promise(function(resolve) {
    consumer.on('ready', function() {
      resolve(consumer);
    });

    consumer.connect();
  });
});

/**
 * The node-rdkafka getReadStream method wrapper, will deserialize
 * the incoming message using the existing schemas.
 *
 * @param {kafka.KafkaConsumer} consumerInstance node-rdkafka instance.
 * @param {string} topic Topic to consume on.
 * @param {Object=} opts Stream options.
 * @return {Stream} A Stream.
 */
Consumer.prototype._getReadStreamWrapper = function (consumerInstance, topic,
  opts) {

  if (!this.valueSchemas[topic]) {
    // topic not found in schemas, bail early
    return consumerInstance.__kafkaAvro_getReadStream(topic, opts);
  }

  var stream = consumerInstance.__kafkaAvro_getReadStream(topic, opts);

  return stream
    .pipe(new Transform({
      objectMode: true,
      transform: this._transformAvro.bind(this, topic),
    }));
};

/**
 * The node-rdkafka on method wrapper, will intercept "data" events and
 * deserialize the incoming message using the existing schemas.
 *
 * @param {kafka.KafkaConsumer} consumerInstance node-rdkafka instance.
 * @param {string} eventName the name to listen for events on.
 * @param {Function} cb Event callback.
 * @private
 */
Consumer.prototype._onWrapper = function (consumerInstance, eventName, cb) {
  if (eventName !== 'data') {
    return consumerInstance.__kafkaAvro_on(eventName, cb);
  }

  consumerInstance.__kafkaAvro_on('data', function(message) {
    if (!this.valueSchemas[message.topic]) {
      console.log('KafkaAvro :: Warning, consumer did not find topic on SR:',
        message.topic);
      message.parsed = JSON.parse(message.toString('utf-8'));

      cb(message);
      return;
    }

    message.parsed = this.valueSchemas[message.topic].fromBuffer(message.value);

    cb(message);
  }.bind(this));
};

/**
 * Callback for Stream Transform, will deserialize the message properly.
 *
 * @param {string} topicName The topic name.
 * @param {*} data The message to encode.
 * @param {string=} encoding Encoding of the message.
 * @param {Function} callback Callback to call when done.
 * @private
 */
Consumer.prototype._transformAvro = function (topicName, data, encoding, callback) {
  if (!this.valueSchemas[topicName]) {
    console.log('KafkaAvro :: Warning, consumer did not find topic on SR:',
      topicName);
    data.parsed = JSON.parse(data.toString('utf-8'));

    callback(null, data);
    return;
  }

  var type = this.valueSchemas[topicName];

  if (this.hasMagicByte) {
    data.parsed = magicByte.fromMessageBuffer(type, data.value);
  } else {
    data.parsed = type.fromBuffer(data.value);
  }
  callback(null, data);
};
<commit_msg>fix magic byte handling on consumer<commit_after>/**
 * @fileOverview Wrapper for node-rdkafka Consumer Ctor, a mixin.
 */
var Transform = require('stream').Transform;

var Promise = require('bluebird');
var cip = require('cip');
var kafka = require('node-rdkafka');

var magicByte = require('./magic-byte');

/**
 * Wrapper for node-rdkafka Consumer Ctor, a mixin.
 *
 * @constructor
 */
var Consumer = module.exports = cip.extend();

/**
 * The wrapper of the node-rdkafka package Consumer Ctor.
 *
 * @param {Object} opts Consumer general options.
 * @see https://github.com/edenhill/librdkafka/blob/2213fb29f98a7a73f22da21ef85e0783f6fd67c4/CONFIGURATION.md
 * @return {Promise(kafka.Consumer)} A Promise with the consumer.
 */
Consumer.prototype.getConsumer = Promise.method(function (opts) {
  if (!opts['metadata.broker.list']) {
    opts['metadata.broker.list'] = this.kafkaBrokerUrl;
  }

  console.log('KafkaAvro :: Starting Consumer with opts:', opts);

  var consumer = new kafka.KafkaConsumer(opts);

  consumer.on('event.log', function(log) {
    console.log('node-rdkafka log:', log);
  });

  // hack node-rdkafka
  consumer.__kafkaAvro_getReadStream = consumer.getReadStream;
  consumer.getReadStream = this._getReadStreamWrapper.bind(this, consumer);

  consumer.__kafkaAvro_on = consumer.on;
  consumer.on = this._onWrapper.bind(this, consumer);

  return new Promise(function(resolve) {
    consumer.on('ready', function() {
      resolve(consumer);
    });

    consumer.connect();
  });
});

/**
 * The node-rdkafka getReadStream method wrapper, will deserialize
 * the incoming message using the existing schemas.
 *
 * @param {kafka.KafkaConsumer} consumerInstance node-rdkafka instance.
 * @param {string} topic Topic to consume on.
 * @param {Object=} opts Stream options.
 * @return {Stream} A Stream.
 */
Consumer.prototype._getReadStreamWrapper = function (consumerInstance, topic,
  opts) {

  if (!this.valueSchemas[topic]) {
    // topic not found in schemas, bail early
    return consumerInstance.__kafkaAvro_getReadStream(topic, opts);
  }

  var stream = consumerInstance.__kafkaAvro_getReadStream(topic, opts);

  return stream
    .pipe(new Transform({
      objectMode: true,
      transform: this._transformAvro.bind(this, topic),
    }));
};

/**
 * The node-rdkafka on method wrapper, will intercept "data" events and
 * deserialize the incoming message using the existing schemas.
 *
 * @param {kafka.KafkaConsumer} consumerInstance node-rdkafka instance.
 * @param {string} eventName the name to listen for events on.
 * @param {Function} cb Event callback.
 * @private
 */
Consumer.prototype._onWrapper = function (consumerInstance, eventName, cb) {
  if (eventName !== 'data') {
    return consumerInstance.__kafkaAvro_on(eventName, cb);
  }

  consumerInstance.__kafkaAvro_on('data', function(message) {
    if (!this.valueSchemas[message.topic]) {
      console.log('KafkaAvro :: Warning, consumer did not find topic on SR:',
        message.topic);
      message.parsed = JSON.parse(message.toString('utf-8'));

      cb(message);
      return;
    }

    var type = this.valueSchemas[message.topic];
    if (this.hasMagicByte) {
      message.parsed = magicByte.fromMessageBuffer(type, message.value).value;
    } else {
      message.parsed = type.fromBuffer(message.value);
    }

    cb(message);
  }.bind(this));
};

/**
 * Callback for Stream Transform, will deserialize the message properly.
 *
 * @param {string} topicName The topic name.
 * @param {*} data The message to encode.
 * @param {string=} encoding Encoding of the message.
 * @param {Function} callback Callback to call when done.
 * @private
 */
Consumer.prototype._transformAvro = function (topicName, data, encoding, callback) {
  if (!this.valueSchemas[topicName]) {
    console.log('KafkaAvro :: Warning, consumer did not find topic on SR:',
      topicName);
    data.parsed = JSON.parse(data.toString('utf-8'));

    callback(null, data);
    return;
  }

  var type = this.valueSchemas[topicName];

  if (this.hasMagicByte) {
    data.parsed = magicByte.fromMessageBuffer(type, data.value).value;
  } else {
    data.parsed = type.fromBuffer(data.value);
  }
  callback(null, data);
};
<|endoftext|>


<commit_before>/**
 * RMUSPreview
 * ===========
 *
 * Klasse für die Preview eines Readmore.de Posts.
 */

function RMUSPreview(){
    /**
     * UserID des aktuellen Benutzers
     * @type {number}
     * @private
     */
    var _userid = 0;

    /**
     * Name des aktuellen Users
     * @type {string}
     * @private
     */
    var _username = '';

    /**
     * Ist die Vorschau aktiv oder nicht?
     * @type {boolean}
     * @private
     */
    var _previewIsEnabled = false;

    /**
     * Tags die in der Vorschau ersetzt werden müssen.
     * @type {{\\[b\\]: string, \\[/b\\]: string, \\[i\\]: string, \\[/i\\]: string, \\[u\\]: string, \\[/u\\]: string, \\[s\\]: string, \\[/s\\]: string, \\[hr\\]: string, \\[center\\]: string, \\[/center\\]: string, \\[spoiler\\]: string, \\[/spoiler\\]: string, \\[youtube]: string, \\[/youtube]: string, \\[img]: string, \\[/img]: string, \\[image]: string, \\[/image]: string, \\[url]: string, \\[/url]: string, \\[/color]: string, \\[list]: string, \\[/list]: string, \\[\\*]: string}}
     * @private
     */
    var _previewTags = {
        '\\[b\\]'           : '
',
        '\\[/b\\]'          : '',
        '\\[i\\]'           : '',
        '\\[/i\\]'          : '',
        '\\[u\\]'           : '',
        '\\[/u\\]'          : '',
        '\\[s\\]'           : '<s>',
        '\\[/s\\]'          : '</s>',
        '\\[hr\\]'          : '
',
        '\\[center\\]'      : '',
        '\\[/center\\]'     : '',
        '\\[spoiler\\]'     : '
Spoiler
',
        '\\[/spoiler\\]'    : '

',
        '\\[youtube]'       : '
',
        '\\[img]'           : '',
        '\\[image]'         : '',
        '\\[url]'           : '',
        '\\[/url]'          : '',
        '\\[/color]'        : '',
        '\\[list]'          : '
',
        '\\[/list]'         : '
',
        '\\[\\*]'           : '
'
    };

    /**
     * jQuery Element der Preview. Damit das entsprechende Element nicht immer wieder ausgelesen werden muss.
     * Wird am ende der _init-Methode gesetzt.
     * @type {{}}
     * @private
     */
    var _previewElement = {};

    /**
     * Wie bei _previewElement: Wird 1x selektiert, damit die Arbeit später entfällt.
     * @type {{}}
     * @private
     */
    var _c_comment = {};

    /**
     * Wie bei _c_comment: Wird 1x selektiert, damit die Arbeit später entfällt.
     * @type {{}}
     * @private
     */
    var _previewtable = {};

    /**
     * Missbrauche diese Funktion einfach mal als Konstuktor-Ersatz. Finde es einfach schöner
     * dafür eine separate Methode zu haben. Wird als letzte Zeile ausgerufen.
     * Liest UserID und Name aus, fügt dann das Grundgerüst für die Preview ein.
     * @private
     */
    _init = function(){
        _readUseridAndUsername();
        _insertPreviewHtml();
        _initializePreview();

        _previewElement = $('#preview');
        _c_comment      = $('#c_comment');
        _previewtable   = $('#previewtable');
    };

    /**
     * Fügt den Button zum ein und ausblenden sowie die Grundlage für das Gerüst der Vorschau
     * auf die Readmore Seite ein.
     * @private
     */
    var _insertPreviewHtml = function () {
        var $div = $('.center:last');

        $('

').insertAfter($div);
        $('<input type="button" value="Vorschau ein-/ausblenden" class="form" id="triggerPreview" style="margin-left: 10px;">').appendTo($div);
    };

    /**
     * Grundgerüst für die Preview einfügen. Userid und Name sind schon ausgelesen, Button zum einblenden ist auch
     * schon vorhanden.
     * @private
     */
    var _initializePreview = function () {
        var d           = new Date();
        var timedata    = {};
        var rows        = [];

        timedata.day     = d.getDate();
        timedata.month   = d.getMonth() + 1;
        timedata.year    = d.getFullYear();
        timedata.hours   = d.getHours();
        timedata.minutes = d.getMinutes();

        $.each(timedata, function (key, value) {
            if (String(value).length == 1) {
                timedata[key] = '0' + value;
            }
        });

        rows.push('#1337
');
        rows.push('' + timedata.day + '.' + timedata.month + '.' + timedata.year + ', ' + timedata.hours + ':' + timedata.minutes + '
');
        rows.push('' + _username + '

');
        rows.push('Beiträge: 1337

');
        rows.push('');

        $('#previewleft').html(rows.join(''));
    };

    /**
     * Liest UserID und UserName aus.
     * @private
     */
    var _readUseridAndUsername = function () {
        var profileLinkHTML = $('div.floatl.vcenter.elf.dgray:eq(1)').html();

        _userid     = profileLinkHTML.match(/id=(.+?)"/)[1];
        _username   = profileLinkHTML.match(/">(.+?)<\/a>/)[1];
    };

    /**
     * Ersetzt den Text mit BBCODE durch HTML-Code.
     * Herzstück der Preview.
     * @param raw_post  {String}    Beitrag mit BBCODE
     * @returns         {String}    Beitrag übersetzt nach HTML
     * @private
     */
    var _convertToPreview = function(raw_post){
        var text            = raw_post;
        var urlPreview      = '';
        var colorPreview    = '';
        var quotes          = '';

        // BB-Code ersetzen
        $.each(_previewTags, function (key, value) {
            var regEx = new RegExp(key, 'g');
            text = text.replace(regEx, value);
        });

        // URL mit Link
        urlPreview = text.match(/\[url=[^\]]+/g);
        if (urlPreview) {
            $.each(urlPreview, function (key) {
                var link    = urlPreview[key].replace(/\[url=/, '').replace(/http:\/\//, '').replace(/https:\/\//, '');
                var regEx   = new RegExp('\\' + urlPreview[key].replace(/\(/g, '\\(').replace(/\)/g, '\\)').replace(/\?/g, '\\?') + '\\]');

                text = text.replace(regEx, '');
            });
        }

        // Color
        colorPreview = text.match(/\[color=[^\]]+/g);
        if (colorPreview) {
            $.each(colorPreview, function (key) {
                var color = text.match(/\[color=(.+?)\]/)[1];
                var regEx = new RegExp('\\' + colorPreview[key] + '\\]');

                text = text.replace(regEx, '');
            });
        }

        // Quote
        quotes = text.match(/\[quot(.*?)\]/g);
        if (quotes != null) {
            $.each(quotes, function (index, value) {
                var quoteHead   = '
Zitat';
                var name        = value.match(/\[quote=(.*?)\]/);

                if (name != null) {
                    quoteHead = quoteHead + ' von ' + name[1];
                }

                quoteHead = quoteHead + ':
';
                text = text.replace(value, quoteHead + '
');
            });
        }

        text = text.replace(/\[\/quote\]/g, '
');
        return text;
    };

    /**
     * Stellt die Preview da
     * @private
     */
    var _showPreview = function(){
        _previewElement.html(_convertToPreview(String(_c_comment.val().replace(/(\r\n|\n|\r)/gm, '
'))));
        console.log('YOO!');
    };

    /**
     * Preview einschalten
     * @private
     */
    var _activatePreview = function(){
        _showPreview();

        _previewtable.css('display', 'block');
        _c_comment.on('keyup', _showPreview);
        _c_comment.on('focus', _showPreview);

        _previewIsEnabled = true;
    };

    /**
     * Preview ausschalten
     * @private
     */
    var _deactivatePreview = function(){
        _previewtable.css('display', 'none');
        _c_comment.off('keyup', _showPreview);
        _c_comment.off('focus', _showPreview);

        _previewIsEnabled = false;
    };

    /**
     * Öffentliche Methode um die Preview ein- oder auszuschalten. Orientiert sich an dem
     * Attribut _previewIsEnabled.
     */
    this.triggerPreview = function() {
        console.log(_previewIsEnabled);

        if (_previewIsEnabled) {
            _deactivatePreview();
        }
        else {
            _activatePreview();
        }
    };

    /**
     * Init Methode aufrufen!
     */
    _init();
}<commit_msg>CHANGE: Remove Debug-Output<commit_after>/**
 * RMUSPreview
 * ===========
 *
 * Klasse für die Preview eines Readmore.de Posts.
 */

function RMUSPreview(){
    /**
     * UserID des aktuellen Benutzers
     * @type {number}
     * @private
     */
    var _userid = 0;

    /**
     * Name des aktuellen Users
     * @type {string}
     * @private
     */
    var _username = '';

    /**
     * Ist die Vorschau aktiv oder nicht?
     * @type {boolean}
     * @private
     */
    var _previewIsEnabled = false;

    /**
     * Tags die in der Vorschau ersetzt werden müssen.
     * @type {{\\[b\\]: string, \\[/b\\]: string, \\[i\\]: string, \\[/i\\]: string, \\[u\\]: string, \\[/u\\]: string, \\[s\\]: string, \\[/s\\]: string, \\[hr\\]: string, \\[center\\]: string, \\[/center\\]: string, \\[spoiler\\]: string, \\[/spoiler\\]: string, \\[youtube]: string, \\[/youtube]: string, \\[img]: string, \\[/img]: string, \\[image]: string, \\[/image]: string, \\[url]: string, \\[/url]: string, \\[/color]: string, \\[list]: string, \\[/list]: string, \\[\\*]: string}}
     * @private
     */
    var _previewTags = {
        '\\[b\\]'           : '
',
        '\\[/b\\]'          : '',
        '\\[i\\]'           : '',
        '\\[/i\\]'          : '',
        '\\[u\\]'           : '',
        '\\[/u\\]'          : '',
        '\\[s\\]'           : '<s>',
        '\\[/s\\]'          : '</s>',
        '\\[hr\\]'          : '
',
        '\\[center\\]'      : '',
        '\\[/center\\]'     : '',
        '\\[spoiler\\]'     : '
Spoiler
',
        '\\[/spoiler\\]'    : '

',
        '\\[youtube]'       : '
',
        '\\[img]'           : '',
        '\\[image]'         : '',
        '\\[url]'           : '',
        '\\[/url]'          : '',
        '\\[/color]'        : '',
        '\\[list]'          : '
',
        '\\[/list]'         : '
',
        '\\[\\*]'           : '
'
    };

    /**
     * jQuery Element der Preview. Damit das entsprechende Element nicht immer wieder ausgelesen werden muss.
     * Wird am ende der _init-Methode gesetzt.
     * @type {{}}
     * @private
     */
    var _previewElement = {};

    /**
     * Wie bei _previewElement: Wird 1x selektiert, damit die Arbeit später entfällt.
     * @type {{}}
     * @private
     */
    var _c_comment = {};

    /**
     * Wie bei _c_comment: Wird 1x selektiert, damit die Arbeit später entfällt.
     * @type {{}}
     * @private
     */
    var _previewtable = {};

    /**
     * Missbrauche diese Funktion einfach mal als Konstuktor-Ersatz. Finde es einfach schöner
     * dafür eine separate Methode zu haben. Wird als letzte Zeile ausgerufen.
     * Liest UserID und Name aus, fügt dann das Grundgerüst für die Preview ein.
     * @private
     */
    _init = function(){
        _readUseridAndUsername();
        _insertPreviewHtml();
        _initializePreview();

        _previewElement = $('#preview');
        _c_comment      = $('#c_comment');
        _previewtable   = $('#previewtable');
    };

    /**
     * Fügt den Button zum ein und ausblenden sowie die Grundlage für das Gerüst der Vorschau
     * auf die Readmore Seite ein.
     * @private
     */
    var _insertPreviewHtml = function () {
        var $div = $('.center:last');

        $('

').insertAfter($div);
        $('<input type="button" value="Vorschau ein-/ausblenden" class="form" id="triggerPreview" style="margin-left: 10px;">').appendTo($div);
    };

    /**
     * Grundgerüst für die Preview einfügen. Userid und Name sind schon ausgelesen, Button zum einblenden ist auch
     * schon vorhanden.
     * @private
     */
    var _initializePreview = function () {
        var d           = new Date();
        var timedata    = {};
        var rows        = [];

        timedata.day     = d.getDate();
        timedata.month   = d.getMonth() + 1;
        timedata.year    = d.getFullYear();
        timedata.hours   = d.getHours();
        timedata.minutes = d.getMinutes();

        $.each(timedata, function (key, value) {
            if (String(value).length == 1) {
                timedata[key] = '0' + value;
            }
        });

        rows.push('#1337
');
        rows.push('' + timedata.day + '.' + timedata.month + '.' + timedata.year + ', ' + timedata.hours + ':' + timedata.minutes + '
');
        rows.push('' + _username + '

');
        rows.push('Beiträge: 1337

');
        rows.push('');

        $('#previewleft').html(rows.join(''));
    };

    /**
     * Liest UserID und UserName aus.
     * @private
     */
    var _readUseridAndUsername = function () {
        var profileLinkHTML = $('div.floatl.vcenter.elf.dgray:eq(1)').html();

        _userid     = profileLinkHTML.match(/id=(.+?)"/)[1];
        _username   = profileLinkHTML.match(/">(.+?)<\/a>/)[1];
    };

    /**
     * Ersetzt den Text mit BBCODE durch HTML-Code.
     * Herzstück der Preview.
     * @param raw_post  {String}    Beitrag mit BBCODE
     * @returns         {String}    Beitrag übersetzt nach HTML
     * @private
     */
    var _convertToPreview = function(raw_post){
        var text            = raw_post;
        var urlPreview      = '';
        var colorPreview    = '';
        var quotes          = '';

        // BB-Code ersetzen
        $.each(_previewTags, function (key, value) {
            var regEx = new RegExp(key, 'g');
            text = text.replace(regEx, value);
        });

        // URL mit Link
        urlPreview = text.match(/\[url=[^\]]+/g);
        if (urlPreview) {
            $.each(urlPreview, function (key) {
                var link    = urlPreview[key].replace(/\[url=/, '').replace(/http:\/\//, '').replace(/https:\/\//, '');
                var regEx   = new RegExp('\\' + urlPreview[key].replace(/\(/g, '\\(').replace(/\)/g, '\\)').replace(/\?/g, '\\?') + '\\]');

                text = text.replace(regEx, '');
            });
        }

        // Color
        colorPreview = text.match(/\[color=[^\]]+/g);
        if (colorPreview) {
            $.each(colorPreview, function (key) {
                var color = text.match(/\[color=(.+?)\]/)[1];
                var regEx = new RegExp('\\' + colorPreview[key] + '\\]');

                text = text.replace(regEx, '');
            });
        }

        // Quote
        quotes = text.match(/\[quot(.*?)\]/g);
        if (quotes != null) {
            $.each(quotes, function (index, value) {
                var quoteHead   = '
Zitat';
                var name        = value.match(/\[quote=(.*?)\]/);

                if (name != null) {
                    quoteHead = quoteHead + ' von ' + name[1];
                }

                quoteHead = quoteHead + ':
';
                text = text.replace(value, quoteHead + '
');
            });
        }

        text = text.replace(/\[\/quote\]/g, '
');
        return text;
    };

    /**
     * Stellt die Preview da
     * @private
     */
    var _showPreview = function(){
        _previewElement.html(_convertToPreview(String(_c_comment.val().replace(/(\r\n|\n|\r)/gm, '
'))));
    };

    /**
     * Preview einschalten
     * @private
     */
    var _activatePreview = function(){
        _showPreview();

        _previewtable.css('display', 'block');
        _c_comment.on('keyup', _showPreview);
        _c_comment.on('focus', _showPreview);

        _previewIsEnabled = true;
    };

    /**
     * Preview ausschalten
     * @private
     */
    var _deactivatePreview = function(){
        _previewtable.css('display', 'none');
        _c_comment.off('keyup', _showPreview);
        _c_comment.off('focus', _showPreview);

        _previewIsEnabled = false;
    };

    /**
     * Öffentliche Methode um die Preview ein- oder auszuschalten. Orientiert sich an dem
     * Attribut _previewIsEnabled.
     */
    this.triggerPreview = function() {
        console.log(_previewIsEnabled);

        if (_previewIsEnabled) {
            _deactivatePreview();
        }
        else {
            _activatePreview();
        }
    };

    /**
     * Init Methode aufrufen!
     */
    _init();
}<|endoftext|>


<commit_before>import _ from 'lodash';
import Handlebars from 'handlebars';

import Path from 'path';
import Util from 'util';

export default function createHandlebars() {
    let handlebars = Handlebars.create();

    handlebars.registerHelper('lowerCase', value => value ? new Handlebars.SafeString(value.toString().toLowerCase()) : '');
    handlebars.registerHelper('upperCase', value => value ? new Handlebars.SafeString(value.toString().toUpperCase()) : '');
    handlebars.registerHelper('lowerCaseFirst', value => value ? new Handlebars.SafeString(_.lowerFirst(value.toString())) : '');
    handlebars.registerHelper('upperCaseFirst', value => value ? new Handlebars.SafeString(_.upperFirst(value.toString())) : '');
    handlebars.registerHelper('camelCase', value => value ? new Handlebars.SafeString(_.camelCase(value.toString())) : '');
    handlebars.registerHelper('kebabCase', value => value ? new Handlebars.SafeString(_.kebabCase(value.toString())) : '');
    handlebars.registerHelper('snakeCase', value => value ? new Handlebars.SafeString(_.snakeCase(value.toString())) : '');
    handlebars.registerHelper('resolvePath', value => value ? new Handlebars.SafeString(Path.resolve(value.toString())) : '');

    handlebars.registerHelper('format', (value, ...params) => {
        params.pop();

        return value ? new Handlebars.SafeString(Util.format(value.toString(), ...params)) : '';
    });

    handlebars.registerHelper('getListType', function getListType(type, options) {
        if (_.startsWith(type.toString(), 'list:')) {
            return new Handlebars.SafeString(type.toString().slice(5));
        }
        else {
            return '';
        }
    });

    handlebars.registerHelper('isType', function isType(type, targetType, options) {
        if (type.toString() === targetType) {
            return options.fn(this);
        }
        else {
            return options.inverse(this);
        }
    });
    handlebars.registerHelper('isTypeString', function isTypeString(type, options) {
        switch (type.toString()) {
            case 'str':
            case 'string':
                return options.fn(this);
            default:
                return options.inverse(this);
        }
    });
    handlebars.registerHelper('isTypeBoolean', function isTypeBoolean(type, options) {
        switch (type.toString()) {
            case 'bool':
            case 'boolean':
                return options.fn(this);
            default:
                return options.inverse(this);
        }
    });
    handlebars.registerHelper('isTypeInteger', function isTypeInteger(type, options) {
        switch (type.toString()) {
            case 'int':
            case 'integer':
                return options.fn(this);
            default:
                return options.inverse(this);
        }
    });
    handlebars.registerHelper('isTypeFloat', function isTypeFloat(type, options) {
        switch (type.toString()) {
            case 'float':
            case 'single':
                return options.fn(this);
            default:
                return options.inverse(this);
        }
    });
    handlebars.registerHelper('isTypeDouble', function isTypeDouble(type, options) {
        switch (type.toString()) {
            case 'double':
                return options.fn(this);
            default:
                return options.inverse(this);
        }
    });
    handlebars.registerHelper('isTypeEnum', function isTypeEnum(type, options) {
        let { root: definition } = options.data;

        if (_.some(definition.enums, e => e.name === type.toString())) {
            return options.fn(this);
        }
        else {
            return options.inverse(this);
        }
    });
    handlebars.registerHelper('isTypeModel', function isTypeModel(type, options) {
        let { root: definition } = options.data;

        if (_.some(definition.models, e => e.name === type.toString())) {
            return options.fn(this);
        }
        else {
            return options.inverse(this);
        }
    });
    handlebars.registerHelper('isTypeList', function isTypeList(type, options) {
        let { root: definition } = options.data;

        if (_.startsWith(type.toString(), 'list:')) {
            return options.fn(this);
        }
        else {
            return options.inverse(this);
        }
    });
    handlebars.registerHelper('isTypeUnsafe', function isTypeList(type, options) {
        let { root: definition } = options.data;

        if (_.startsWith(type.toString(), 'unsafe:')) {
            return options.fn(this);
        }
        else {
            return options.inverse(this);
        }
    });

    return handlebars;
}<commit_msg>Added array type check.  Added array and unsafe type unwrappers.<commit_after>import _ from 'lodash';
import Handlebars from 'handlebars';

import Path from 'path';
import Util from 'util';

export default function createHandlebars() {
    let handlebars = Handlebars.create();

    handlebars.registerHelper('lowerCase', value => value ? new Handlebars.SafeString(value.toString().toLowerCase()) : '');
    handlebars.registerHelper('upperCase', value => value ? new Handlebars.SafeString(value.toString().toUpperCase()) : '');
    handlebars.registerHelper('lowerCaseFirst', value => value ? new Handlebars.SafeString(_.lowerFirst(value.toString())) : '');
    handlebars.registerHelper('upperCaseFirst', value => value ? new Handlebars.SafeString(_.upperFirst(value.toString())) : '');
    handlebars.registerHelper('camelCase', value => value ? new Handlebars.SafeString(_.camelCase(value.toString())) : '');
    handlebars.registerHelper('kebabCase', value => value ? new Handlebars.SafeString(_.kebabCase(value.toString())) : '');
    handlebars.registerHelper('snakeCase', value => value ? new Handlebars.SafeString(_.snakeCase(value.toString())) : '');
    handlebars.registerHelper('resolvePath', value => value ? new Handlebars.SafeString(Path.resolve(value.toString())) : '');

    handlebars.registerHelper('format', (value, ...params) => {
        params.pop();

        return value ? new Handlebars.SafeString(Util.format(value.toString(), ...params)) : '';
    });

    handlebars.registerHelper('getListType', function getListType(type, options) {
        if (_.startsWith(type.toString(), 'list:')) {
            return new Handlebars.SafeString(type.toString().slice(5));
        }
        else {
            return '';
        }
    });
    handlebars.registerHelper('getArrayType', function getListType(type, options) {
        if (_.startsWith(type.toString(), 'array:')) {
            return new Handlebars.SafeString(type.toString().slice(6));
        }
        else {
            return '';
        }
    });
    handlebars.registerHelper('getUnsafeType', function getListType(type, options) {
        if (_.startsWith(type.toString(), 'unsafe:')) {
            return new Handlebars.SafeString(type.toString().slice(7));
        }
        else {
            return '';
        }
    });

    handlebars.registerHelper('isType', function isType(type, targetType, options) {
        if (type.toString() === targetType) {
            return options.fn(this);
        }
        else {
            return options.inverse(this);
        }
    });
    handlebars.registerHelper('isTypeString', function isTypeString(type, options) {
        switch (type.toString()) {
            case 'str':
            case 'string':
                return options.fn(this);
            default:
                return options.inverse(this);
        }
    });
    handlebars.registerHelper('isTypeBoolean', function isTypeBoolean(type, options) {
        switch (type.toString()) {
            case 'bool':
            case 'boolean':
                return options.fn(this);
            default:
                return options.inverse(this);
        }
    });
    handlebars.registerHelper('isTypeInteger', function isTypeInteger(type, options) {
        switch (type.toString()) {
            case 'int':
            case 'integer':
                return options.fn(this);
            default:
                return options.inverse(this);
        }
    });
    handlebars.registerHelper('isTypeFloat', function isTypeFloat(type, options) {
        switch (type.toString()) {
            case 'float':
            case 'single':
                return options.fn(this);
            default:
                return options.inverse(this);
        }
    });
    handlebars.registerHelper('isTypeDouble', function isTypeDouble(type, options) {
        switch (type.toString()) {
            case 'double':
                return options.fn(this);
            default:
                return options.inverse(this);
        }
    });
    handlebars.registerHelper('isTypeEnum', function isTypeEnum(type, options) {
        let { root: definition } = options.data;

        if (_.some(definition.enums, e => e.name === type.toString())) {
            return options.fn(this);
        }
        else {
            return options.inverse(this);
        }
    });
    handlebars.registerHelper('isTypeModel', function isTypeModel(type, options) {
        let { root: definition } = options.data;

        if (_.some(definition.models, e => e.name === type.toString())) {
            return options.fn(this);
        }
        else {
            return options.inverse(this);
        }
    });
    handlebars.registerHelper('isTypeList', function isTypeList(type, options) {
        let { root: definition } = options.data;

        if (_.startsWith(type.toString(), 'list:')) {
            return options.fn(this);
        }
        else {
            return options.inverse(this);
        }
    });
    handlebars.registerHelper('isTypeArray', function isTypeList(type, options) {
        let { root: definition } = options.data;

        if (_.startsWith(type.toString(), 'array:')) {
            return options.fn(this);
        }
        else {
            return options.inverse(this);
        }
    });
    handlebars.registerHelper('isTypeUnsafe', function isTypeList(type, options) {
        let { root: definition } = options.data;

        if (_.startsWith(type.toString(), 'unsafe:')) {
            return options.fn(this);
        }
        else {
            return options.inverse(this);
        }
    });

    return handlebars;
}<|endoftext|>


<commit_before>var chalk = require('chalk');
var config = require('../config').logger;

// Logger settings
var WRITE_LEVELS = {
  'DEBUG': 1,
  'INFO': 2,
  'WARNING': 3,
  'ERROR': 4,
  'DISABLED': 5
};

// Internal variables
var currentWriteLevel;
var debugStyle;
var infoStyle;
var warningStyle;
var errorStyle;

module.exports = Logger;

/**
 * Wheelie logger
 * @constructor
 * @param {Object} options - Logger configurations
 * @param {Object} options.colors - Defines used color for each write level
 * @param {number} options.writeLevel - Defines the logger write level
 */
function Logger(options) {
  options = options || config;

  var colors = options.colors;
  var writeLevel = options.writeLevel;

  // setting current write level
  this.setWriteLevel(writeLevel);

  // setting console styles
  debugStyle = chalk[colors.DEBUG];
  infoStyle = chalk[colors.INFO];
  warningStyle = chalk[colors.WARNING];
  errorStyle = chalk[colors.ERROR];
}

/**
 * Prints to stdout with newline, using the DEBUG chalk style
 * @param {string} text - Text to print
 */
Logger.prototype.debug = function(text) {
  if (this.writeIsEnabled('DEBUG')) {
    console.debug(debugStyle(text));
  }
}

/**
 * Prints to stdout with newline, using the INFO chalk style
 * @param {string} text - Text to print
 */
Logger.prototype.info = function(text) {
  if (this.writeIsEnabled('INFO')) {
    console.info(infoStyle(text));
  }
}

/**
 * Prints to stdout with newline, using the WARNING chalk style
 * @param {string} text - Text to print
 */
Logger.prototype.warning = function(text) {
  if (this.writeIsEnabled('WARNING')) {
    console.warn(debugStyle(text));
  }
}

/**
 * Prints to stdout with newline, using the ERROR chalk style
 * @param {string} text - Text to print
 */
Logger.prototype.error = function(text) {
  if (this.writeIsEnabled('ERROR')) {
    console.error(errorStyle(text));
  }
}

/**
 * Sets the current write level; returns an error if writeLevel doesn't exist
 * @param {string} writeLevel - The new write level
 */
Logger.prototype.setWriteLevel = function(writeLevel) {
  if (WRITE_LEVELS[writeLevel] === undefined) {
    throw new Error("Selected write level '" +  writeLevel + "' doesn't exist");
  } else {
    currentWriteLevel = writeLevel;
  }
}

/**
 * Returns the current write level
 * @return {string} the current write level
 */
Logger.prototype.getWriteLevel = function() {
  return currentWriteLevel;
}

/**
 * Returns if this write level is enabled, according to the current write level
 * @param {string} writeLevel - Write level to test
 */
Logger.prototype.writeIsEnabled = function(writeLevel) {
  return WRITE_LEVELS[writeLevel] > WRITE_LEVELS[currentWriteLevel];
}
<commit_msg>writeIsEnabled works as expected<commit_after>var chalk = require('chalk');
var config = require('../config').logger;

// Logger settings
var WRITE_LEVELS = {
  'DEBUG': 1,
  'INFO': 2,
  'WARNING': 3,
  'ERROR': 4,
  'DISABLED': 5
};

// Internal variables
var currentWriteLevel;
var debugStyle;
var infoStyle;
var warningStyle;
var errorStyle;

module.exports = Logger;

/**
 * Wheelie logger
 * @constructor
 * @param {Object} options - Logger configurations
 * @param {Object} options.colors - Defines used color for each write level
 * @param {number} options.writeLevel - Defines the logger write level
 */
function Logger(options) {
  options = options || config;

  var colors = options.colors;
  var writeLevel = options.writeLevel;

  // setting current write level
  this.setWriteLevel(writeLevel);

  // setting console styles
  debugStyle = chalk[colors.DEBUG];
  infoStyle = chalk[colors.INFO];
  warningStyle = chalk[colors.WARNING];
  errorStyle = chalk[colors.ERROR];
}

/**
 * Prints to stdout with newline, using the DEBUG chalk style
 * @param {string} text - Text to print
 */
Logger.prototype.debug = function(text) {
  if (this.writeIsEnabled('DEBUG')) {
    console.debug(debugStyle(text));
  }
}

/**
 * Prints to stdout with newline, using the INFO chalk style
 * @param {string} text - Text to print
 */
Logger.prototype.info = function(text) {
  if (this.writeIsEnabled('INFO')) {
    console.info(infoStyle(text));
  }
}

/**
 * Prints to stdout with newline, using the WARNING chalk style
 * @param {string} text - Text to print
 */
Logger.prototype.warning = function(text) {
  if (this.writeIsEnabled('WARNING')) {
    console.warn(debugStyle(text));
  }
}

/**
 * Prints to stdout with newline, using the ERROR chalk style
 * @param {string} text - Text to print
 */
Logger.prototype.error = function(text) {
  if (this.writeIsEnabled('ERROR')) {
    console.error(errorStyle(text));
  }
}

/**
 * Sets the current write level; returns an error if writeLevel doesn't exist
 * @param {string} writeLevel - The new write level
 */
Logger.prototype.setWriteLevel = function(writeLevel) {
  if (WRITE_LEVELS[writeLevel] === undefined) {
    throw new Error("Selected write level '" +  writeLevel + "' doesn't exist");
  } else {
    currentWriteLevel = writeLevel;
  }
}

/**
 * Returns the current write level
 * @return {string} the current write level
 */
Logger.prototype.getWriteLevel = function() {
  return currentWriteLevel;
}

/**
 * Returns if this write level is enabled, according to the current write level
 * @param {string} writeLevel - Write level to test
 */
Logger.prototype.writeIsEnabled = function(writeLevel) {
  return WRITE_LEVELS[writeLevel] >= WRITE_LEVELS[currentWriteLevel];
}
<|endoftext|>


<commit_before>var fs = require('fs');
var crypto = require('crypto');
var Step = require('step');
var path = require('path');
var url = require('url');
var mapnik = require('mapnik');
var qs = require('querystring');
var Pool = require('generic-pool').Pool;
var LockingCache = require('./lockingcache');

var cache = {};

if (process.platform !== 'win32') {
    var major_version = parseInt(process.versions.node.split('.')[0],10);
    var minor_version = parseInt(process.versions.node.split('.')[1],10);
    // older node versions support eio, newer need UV_THREADPOOL_SIZE set
    if (major_version == 0 && minor_version < 9) {
        // Increase number of threads to 1.5x the number of logical CPUs.
        var threads = Math.ceil(Math.max(4, require('os').cpus().length * 1.5));
        require('eio').setMinParallel(threads);
    }
}

exports = module.exports = MapnikSource;

require('util').inherits(MapnikSource, require('events').EventEmitter);
function MapnikSource(uri, callback) {
    uri = this._normalizeURI(uri);
    var key = url.format(uri);

    if (uri.protocol && uri.protocol !== 'mapnik:') {
        throw new Error('Only the mapnik protocol is supported');
    }
    // by default we use an internal self-caching mechanism but
    // calling applications can pass `internal_cache:false` to disable
    // TODO - consider removing completely once https://github.com/mapbox/tilemill/issues/1893
    // is in place and a solid reference implementation of external caching
    if (uri.query.internal_cache === false) {
        this.once('open', callback);
        this._open(uri);
    } else {
        // https://github.com/mapbox/tilelive-mapnik/issues/47
        if (!cache[key]) {
            cache[key] = this;
            this._self_cache_key = key;
            this._open(uri);
        }
        var source = cache[key];
        source.setMaxListeners(0);
        if (!source.open) {
            source.once('open', function(err, source) {
                if (err) cache[key] = false;
                callback(err, source);
            });
        } else {
            callback(null, source);
        }
    }
    // cache used to skip encoding of solid images
    this.solidCache = {};
    return undefined;
}

MapnikSource.mapnik = mapnik;

MapnikSource.prototype.toJSON = function() {
    return url.format(this._uri);
};

MapnikSource.prototype._normalizeURI = function(uri) {
    if (typeof uri === 'string') uri = url.parse(uri, true);
    if (uri.hostname === '.' || uri.hostname == '..') {
        uri.pathname = uri.hostname + uri.pathname;
        delete uri.hostname;
        delete uri.host;
    }
    uri.pathname = path.resolve(uri.pathname);
    uri.query = uri.query || {};
    if (typeof uri.query === 'string') uri.query = qs.parse(uri.query);
    if (typeof uri.query.internal_cache === "undefined") uri.query.internal_cache = true;
    if (!uri.query.base) uri.query.base = '';
    if (!uri.query.metatile) uri.query.metatile = 2;
    if (!uri.query.resolution) uri.query.resolution = 4;
    if (!uri.query.bufferSize) uri.query.bufferSize = 128;
    if (!uri.query.tileSize) uri.query.tileSize = 256;
    if (!uri.query.scale) uri.query.scale = 1;
    return uri;
};

// Finds all XML files in the filepath and returns their tilesource URI.
MapnikSource.list = function(filepath, callback) {
    filepath = path.resolve(filepath);
    fs.readdir(filepath, function(err, files) {
        if (err) return callback(err);
        for (var result = {}, i = 0; i < files.length; i++) {
            var name = files[i].match(/^([\w-]+)\.xml$/);
            if (name) result[name[1]] = 'mapnik://' + path.join(filepath, name[0]);
        }
        return callback(null, result);
    });
};

// Finds an XML file with the given ID in the filepath and returns a
// tilesource URI.
MapnikSource.findID = function(filepath, id, callback) {
    filepath = path.resolve(filepath);
    var file = path.join(filepath, id + '.xml');
    fs.stat(file, function(err, stats) {
        if (err) return callback(err);
        else return callback(null, 'mapnik://' + file);
    });
};

MapnikSource.prototype._open = function(uri) {
    var source = this;
    function error(err) {
        process.nextTick(function() {
            source.emit('open', err);
        });
    }

    this._stats = {
        render: 0,          // # of times a render is requested from mapnik
        total: 0,           // # of tiles returned from source
        encoded: 0,         // # of tiles encoded
        solid: 0,           // # of tiles isSolid
        solidPainted: 0     // # of tiles isSolid && painted
    };
    this._internal_cache = uri.query.internal_cache;
    this._base = uri.query.base;
    uri.query.metatile = +uri.query.metatile;
    uri.query.resolution = +uri.query.resolution;
    uri.query.bufferSize = +uri.query.bufferSize;
    uri.query.tileSize = +uri.query.tileSize;
    this._uri = uri;

    // Public API to announce how we're metatiling.
    this.metatile = uri.query.metatile;
    this.bufferSize = uri.query.bufferSize;

    // Initialize this map. This wraps `localize()` and calls `create()`
    // afterwards to actually create a new Mapnik map object.
    Step(function() {
        source._loadXML(this);
    }, function(err, xml) {
        if (err) return error(err);
        // https://github.com/mapbox/tilelive-mapnik/issues/25
        // there seems to be no value to assinging xml to a property
        //source._xml = xml;
        source._createMetatileCache();
        source._createPool(xml, this);
    }, function(err) {
        if (err) return error(err);
        source._populateInfo(this);
    }, function(err) {
        if (err) return error(err);
        source.open = true;
        source.emit('open', null, source);
    });
};

MapnikSource.prototype.close = function(callback) {
    this._close(function() {
        return callback();
    });
};

MapnikSource.prototype._cache = cache;

MapnikSource.prototype._close = function(callback) {
    if (cache[this._self_cache_key]) delete cache[this._self_cache_key];
    if (this._tileCache) this._tileCache.clear();
    // https://github.com/coopernurse/node-pool/issues/17#issuecomment-6565795
    if (this._pool) {
        var pool = this._pool;
        // NOTE: .drain() and .destoryAllNow() do
        // not pass error args
        pool.drain(function() {
            pool.destroyAllNow(function() {
                return callback();
            });
        });
    }
};

MapnikSource.registerProtocols = function(tilelive) {
    tilelive.protocols['mapnik:'] = MapnikSource;
};

// Loads the XML file from the specified path. Calls `callback` when the mapfile
// can be expected to be in `mapfile`. If this isn't successful, `callback` gets
// an error as its first argument.
MapnikSource.prototype._loadXML = function(callback) {
    var source = this;
    this._base = path.resolve(path.dirname(this._uri.pathname));

    // This is a string-based map file. Pass it on literally.
    if (this._uri.xml) return callback(null, this._uri.xml);

    // Load XML from file.
    fs.readFile(path.resolve(source._uri.pathname), 'utf8', function(err, xml) {
        if (err) return callback(err);
        callback(null, xml);
    });
};

// Create a new mapnik map object at `this.mapnik`. Requires that the mapfile
// be localized with `this.localize()`. This can be called in repetition because
// it won't recreate `this.mapnik`.
MapnikSource.prototype._createPool = function(xml, callback) {
    var source = this;
    if (!this._pool) this._pool = Pool({
        create: function(callback) {
            var map = new mapnik.Map(source._uri.query.tileSize, source._uri.query.tileSize);
            map.bufferSize = source._uri.query.bufferSize;
            var opts = {strict: false, base: source._base + '/'};
            //https://github.com/mapbox/tilelive-mapnik/issues/58
            try {
                map.fromStringSync(xml, opts);
                return callback(null, map);
            } catch (err) {
                return callback(err);
            }
        },
        destroy: function(map) {
            delete map;
        },
        // @TODO: need a smarter way to scale this. More
        // maps in pool seems better for PostGIS.
        max: require('os').cpus().length
    });
    callback(null);
};

MapnikSource.prototype._populateInfo = function(callback) {
    var source = this;
    var id = path.basename(this._uri.pathname, path.extname(this._uri.pathname));

    this._pool.acquire(function(err, map) {
        if (err) return callback(err);

        var info = { id: id, name: id, minzoom: 0, maxzoom: 22 };

        var p = map.parameters;
        for (var key in p) info[key] = p[key];
        if (p.bounds) info.bounds = p.bounds.split(',').map(parseFloat);
        if (p.center) info.center = p.center.split(',').map(parseFloat);
        if (p.minzoom) info.minzoom = parseInt(p.minzoom, 10);
        if (p.maxzoom) info.maxzoom = parseInt(p.maxzoom, 10);
        if (p.interactivity_fields) info.interactivity_fields = p.interactivity_fields.split(',');

        if (!info.bounds || info.bounds.length !== 4)
            info.bounds = [ -180, -85.05112877980659, 180, 85.05112877980659 ];

        if (!info.center || info.center.length !== 3) info.center = [
            (info.bounds[2] - info.bounds[0]) / 2 + info.bounds[0],
            (info.bounds[3] - info.bounds[1]) / 2 + info.bounds[1],
            2 ];

        source._info = info;
        source._pool.release(map);
        callback(null)
    });
};

// Creates a locking cache that generates tiles. When requesting the same tile
// multiple times, they'll be grouped to one request.
MapnikSource.prototype._createMetatileCache = function() {
    var source = this;
    this._tileCache = new LockingCache(function(key) {
        var cache = this;
        var coords = key.split(',');
        var keys = source._renderMetatile({
            metatile: source._uri.query.metatile,
            tileSize: source._uri.query.tileSize,
            format: coords[0],
            z: +coords[1],
            x: +coords[2],
            y: +coords[3]
        }, function(err, tiles) {
            if (err) {
                // Push error objects to all entries that were supposed to
                // be generated.
                keys.forEach(function(key) {
                    cache.put(key, err);
                });
            } else {
                // Put all the generated tiles into the locking cache.
                for (var key in tiles) {
                    cache.put(key, null, tiles[key].image, tiles[key].headers);
                }
            }
        });

        // Return a list of all the tile coordinates that are being rendered
        // as part of this metatile.
        return keys;
    },
    0); // purge immediately after callbacks
};

// Render handler for a given tile request.
MapnikSource.prototype.getTile = function(z, x, y, callback) {
    z = +z; x = +x; y = +y;
    if (isNaN(z) || isNaN(x) || isNaN(y)) {
        return callback(new Error('Invalid coordinates: '+z+'/'+x+'/'+y));
    }

    var max = (1 << z);
    if (x >= max || x < 0 || y >= max || y < 0) {
        return callback(new Error('Coordinates out of range: '+z+'/'+x+'/'+y));
    }

    var format = (this._info && this._info.format) || 'png';
    var key = [format, z, x, y].join(',');
    this._tileCache.get(key, function(err, tile, headers) {
        if (err) return callback(err);
        callback(null, tile, headers);
    });
};

MapnikSource.prototype.getGrid = function(z, x, y, callback) {
    z = +z; x = +x; y = +y;
    if (isNaN(z) || isNaN(x) || isNaN(y)) {
        return callback(new Error('Invalid coordinates: '+z+'/'+x+'/'+y));
    }

    var max = (1 << z);
    if (x >= max || x < 0 || y >= max || y < 0) {
        return callback(new Error('Coordinates out of range: '+z+'/'+x+'/'+y));
    } else if (!this._info ||
        !this._info.interactivity_fields ||
        !this._info.interactivity_layer) {
        if (!this._info) {
            return callback(new Error('Tilesource info is missing, cannot rendering interactivity'));
        } else {
            return callback(new Error('Tileset has no interactivity'));
        }
    } else if (!mapnik.supports.grid) {
        return callback(new Error('Mapnik is missing grid support'));
    }

    var key = ['utf', z, x, y].join(',');
    this._tileCache.get(key, function(err, grid, headers) {
        if (err) return callback(err);
        delete grid.solid;
        callback(null, grid, headers);
    });
};

MapnikSource.prototype.getInfo = function(callback) {
    if (this._info) callback(null, this._info);
    else callback(new Error('Info is unavailable'));
};

// Add other functions.
require('./render');
<commit_msg>Allow users to configure strict mode via URI (#74)<commit_after>var fs = require('fs');
var crypto = require('crypto');
var Step = require('step');
var path = require('path');
var url = require('url');
var mapnik = require('mapnik');
var qs = require('querystring');
var Pool = require('generic-pool').Pool;
var LockingCache = require('./lockingcache');

var cache = {};

if (process.platform !== 'win32') {
    var major_version = parseInt(process.versions.node.split('.')[0],10);
    var minor_version = parseInt(process.versions.node.split('.')[1],10);
    // older node versions support eio, newer need UV_THREADPOOL_SIZE set
    if (major_version == 0 && minor_version < 9) {
        // Increase number of threads to 1.5x the number of logical CPUs.
        var threads = Math.ceil(Math.max(4, require('os').cpus().length * 1.5));
        require('eio').setMinParallel(threads);
    }
}

exports = module.exports = MapnikSource;

require('util').inherits(MapnikSource, require('events').EventEmitter);
function MapnikSource(uri, callback) {
    uri = this._normalizeURI(uri);
    var key = url.format(uri);

    if (uri.protocol && uri.protocol !== 'mapnik:') {
        throw new Error('Only the mapnik protocol is supported');
    }
    // by default we use an internal self-caching mechanism but
    // calling applications can pass `internal_cache:false` to disable
    // TODO - consider removing completely once https://github.com/mapbox/tilemill/issues/1893
    // is in place and a solid reference implementation of external caching
    if (uri.query.internal_cache === false) {
        this.once('open', callback);
        this._open(uri);
    } else {
        // https://github.com/mapbox/tilelive-mapnik/issues/47
        if (!cache[key]) {
            cache[key] = this;
            this._self_cache_key = key;
            this._open(uri);
        }
        var source = cache[key];
        source.setMaxListeners(0);
        if (!source.open) {
            source.once('open', function(err, source) {
                if (err) cache[key] = false;
                callback(err, source);
            });
        } else {
            callback(null, source);
        }
    }
    // cache used to skip encoding of solid images
    this.solidCache = {};
    return undefined;
}

MapnikSource.mapnik = mapnik;

MapnikSource.prototype.toJSON = function() {
    return url.format(this._uri);
};

MapnikSource.prototype._normalizeURI = function(uri) {
    if (typeof uri === 'string') uri = url.parse(uri, true);
    if (uri.hostname === '.' || uri.hostname == '..') {
        uri.pathname = uri.hostname + uri.pathname;
        delete uri.hostname;
        delete uri.host;
    }
    uri.pathname = path.resolve(uri.pathname);
    uri.query = uri.query || {};
    if (typeof uri.query === 'string') uri.query = qs.parse(uri.query);
    if (typeof uri.query.internal_cache === "undefined") uri.query.internal_cache = true;
    if (!uri.query.base) uri.query.base = '';
    if (!uri.query.metatile) uri.query.metatile = 2;
    if (!uri.query.resolution) uri.query.resolution = 4;
    if (!uri.query.bufferSize) uri.query.bufferSize = 128;
    if (!uri.query.tileSize) uri.query.tileSize = 256;
    if (!uri.query.scale) uri.query.scale = 1;
    return uri;
};

// Finds all XML files in the filepath and returns their tilesource URI.
MapnikSource.list = function(filepath, callback) {
    filepath = path.resolve(filepath);
    fs.readdir(filepath, function(err, files) {
        if (err) return callback(err);
        for (var result = {}, i = 0; i < files.length; i++) {
            var name = files[i].match(/^([\w-]+)\.xml$/);
            if (name) result[name[1]] = 'mapnik://' + path.join(filepath, name[0]);
        }
        return callback(null, result);
    });
};

// Finds an XML file with the given ID in the filepath and returns a
// tilesource URI.
MapnikSource.findID = function(filepath, id, callback) {
    filepath = path.resolve(filepath);
    var file = path.join(filepath, id + '.xml');
    fs.stat(file, function(err, stats) {
        if (err) return callback(err);
        else return callback(null, 'mapnik://' + file);
    });
};

MapnikSource.prototype._open = function(uri) {
    var source = this;
    function error(err) {
        process.nextTick(function() {
            source.emit('open', err);
        });
    }

    this._stats = {
        render: 0,          // # of times a render is requested from mapnik
        total: 0,           // # of tiles returned from source
        encoded: 0,         // # of tiles encoded
        solid: 0,           // # of tiles isSolid
        solidPainted: 0     // # of tiles isSolid && painted
    };
    this._internal_cache = uri.query.internal_cache;
    this._base = uri.query.base;
    uri.query.metatile = +uri.query.metatile;
    uri.query.resolution = +uri.query.resolution;
    uri.query.bufferSize = +uri.query.bufferSize;
    uri.query.tileSize = +uri.query.tileSize;
    this._uri = uri;

    // Public API to announce how we're metatiling.
    this.metatile = uri.query.metatile;
    this.bufferSize = uri.query.bufferSize;

    // Initialize this map. This wraps `localize()` and calls `create()`
    // afterwards to actually create a new Mapnik map object.
    Step(function() {
        source._loadXML(this);
    }, function(err, xml) {
        if (err) return error(err);
        // https://github.com/mapbox/tilelive-mapnik/issues/25
        // there seems to be no value to assinging xml to a property
        //source._xml = xml;
        source._createMetatileCache();
        source._createPool(xml, this);
    }, function(err) {
        if (err) return error(err);
        source._populateInfo(this);
    }, function(err) {
        if (err) return error(err);
        source.open = true;
        source.emit('open', null, source);
    });
};

MapnikSource.prototype.close = function(callback) {
    this._close(function() {
        return callback();
    });
};

MapnikSource.prototype._cache = cache;

MapnikSource.prototype._close = function(callback) {
    if (cache[this._self_cache_key]) delete cache[this._self_cache_key];
    if (this._tileCache) this._tileCache.clear();
    // https://github.com/coopernurse/node-pool/issues/17#issuecomment-6565795
    if (this._pool) {
        var pool = this._pool;
        // NOTE: .drain() and .destoryAllNow() do
        // not pass error args
        pool.drain(function() {
            pool.destroyAllNow(function() {
                return callback();
            });
        });
    }
};

MapnikSource.registerProtocols = function(tilelive) {
    tilelive.protocols['mapnik:'] = MapnikSource;
};

// Loads the XML file from the specified path. Calls `callback` when the mapfile
// can be expected to be in `mapfile`. If this isn't successful, `callback` gets
// an error as its first argument.
MapnikSource.prototype._loadXML = function(callback) {
    var source = this;
    this._base = path.resolve(path.dirname(this._uri.pathname));

    // This is a string-based map file. Pass it on literally.
    if (this._uri.xml) return callback(null, this._uri.xml);

    // Load XML from file.
    fs.readFile(path.resolve(source._uri.pathname), 'utf8', function(err, xml) {
        if (err) return callback(err);
        callback(null, xml);
    });
};

// Create a new mapnik map object at `this.mapnik`. Requires that the mapfile
// be localized with `this.localize()`. This can be called in repetition because
// it won't recreate `this.mapnik`.
MapnikSource.prototype._createPool = function(xml, callback) {
    var source = this;
    if (!this._pool) this._pool = Pool({
        create: function(callback) {
            var map = new mapnik.Map(source._uri.query.tileSize, source._uri.query.tileSize);
            map.bufferSize = source._uri.query.bufferSize;
            var opts = {strict: source._uri.strict || false, base: source._base + '/'};
            //https://github.com/mapbox/tilelive-mapnik/issues/58
            try {
                map.fromStringSync(xml, opts);
                return callback(null, map);
            } catch (err) {
                return callback(err);
            }
        },
        destroy: function(map) {
            delete map;
        },
        // @TODO: need a smarter way to scale this. More
        // maps in pool seems better for PostGIS.
        max: require('os').cpus().length
    });
    callback(null);
};

MapnikSource.prototype._populateInfo = function(callback) {
    var source = this;
    var id = path.basename(this._uri.pathname, path.extname(this._uri.pathname));

    this._pool.acquire(function(err, map) {
        if (err) return callback(err);

        var info = { id: id, name: id, minzoom: 0, maxzoom: 22 };

        var p = map.parameters;
        for (var key in p) info[key] = p[key];
        if (p.bounds) info.bounds = p.bounds.split(',').map(parseFloat);
        if (p.center) info.center = p.center.split(',').map(parseFloat);
        if (p.minzoom) info.minzoom = parseInt(p.minzoom, 10);
        if (p.maxzoom) info.maxzoom = parseInt(p.maxzoom, 10);
        if (p.interactivity_fields) info.interactivity_fields = p.interactivity_fields.split(',');

        if (!info.bounds || info.bounds.length !== 4)
            info.bounds = [ -180, -85.05112877980659, 180, 85.05112877980659 ];

        if (!info.center || info.center.length !== 3) info.center = [
            (info.bounds[2] - info.bounds[0]) / 2 + info.bounds[0],
            (info.bounds[3] - info.bounds[1]) / 2 + info.bounds[1],
            2 ];

        source._info = info;
        source._pool.release(map);
        callback(null)
    });
};

// Creates a locking cache that generates tiles. When requesting the same tile
// multiple times, they'll be grouped to one request.
MapnikSource.prototype._createMetatileCache = function() {
    var source = this;
    this._tileCache = new LockingCache(function(key) {
        var cache = this;
        var coords = key.split(',');
        var keys = source._renderMetatile({
            metatile: source._uri.query.metatile,
            tileSize: source._uri.query.tileSize,
            format: coords[0],
            z: +coords[1],
            x: +coords[2],
            y: +coords[3]
        }, function(err, tiles) {
            if (err) {
                // Push error objects to all entries that were supposed to
                // be generated.
                keys.forEach(function(key) {
                    cache.put(key, err);
                });
            } else {
                // Put all the generated tiles into the locking cache.
                for (var key in tiles) {
                    cache.put(key, null, tiles[key].image, tiles[key].headers);
                }
            }
        });

        // Return a list of all the tile coordinates that are being rendered
        // as part of this metatile.
        return keys;
    },
    0); // purge immediately after callbacks
};

// Render handler for a given tile request.
MapnikSource.prototype.getTile = function(z, x, y, callback) {
    z = +z; x = +x; y = +y;
    if (isNaN(z) || isNaN(x) || isNaN(y)) {
        return callback(new Error('Invalid coordinates: '+z+'/'+x+'/'+y));
    }

    var max = (1 << z);
    if (x >= max || x < 0 || y >= max || y < 0) {
        return callback(new Error('Coordinates out of range: '+z+'/'+x+'/'+y));
    }

    var format = (this._info && this._info.format) || 'png';
    var key = [format, z, x, y].join(',');
    this._tileCache.get(key, function(err, tile, headers) {
        if (err) return callback(err);
        callback(null, tile, headers);
    });
};

MapnikSource.prototype.getGrid = function(z, x, y, callback) {
    z = +z; x = +x; y = +y;
    if (isNaN(z) || isNaN(x) || isNaN(y)) {
        return callback(new Error('Invalid coordinates: '+z+'/'+x+'/'+y));
    }

    var max = (1 << z);
    if (x >= max || x < 0 || y >= max || y < 0) {
        return callback(new Error('Coordinates out of range: '+z+'/'+x+'/'+y));
    } else if (!this._info ||
        !this._info.interactivity_fields ||
        !this._info.interactivity_layer) {
        if (!this._info) {
            return callback(new Error('Tilesource info is missing, cannot rendering interactivity'));
        } else {
            return callback(new Error('Tileset has no interactivity'));
        }
    } else if (!mapnik.supports.grid) {
        return callback(new Error('Mapnik is missing grid support'));
    }

    var key = ['utf', z, x, y].join(',');
    this._tileCache.get(key, function(err, grid, headers) {
        if (err) return callback(err);
        delete grid.solid;
        callback(null, grid, headers);
    });
};

MapnikSource.prototype.getInfo = function(callback) {
    if (this._info) callback(null, this._info);
    else callback(new Error('Info is unavailable'));
};

// Add other functions.
require('./render');
<|endoftext|>


<commit_before>'use strict';

import isPlainObject from 'lodash.isplainobject';

/**
 * Merge BEMJSON-object with tag/mods/mix props
 * delivered from component instance creation.
 *
 * @param {Object} json
 * @param {Object} props
 * @return {Object}
 */
export default function(json, props = {}) {
    json.tag = props.tag || json.tag;

    json.mods = Object.assign({}, json.mods, props.mods);

    if (props.mix) {
        json.mix = json.mix || [];

        // `mix` as object
        if (isPlainObject(json.mix)) {
            json.mix = [ json.mix ];
        }

        json.mix = json.mix.concat(props.mix);
    }

    return json;
};
<commit_msg>:heavy_check_mark: allow to set `block` and `elem` from props<commit_after>'use strict';

import isPlainObject from 'lodash.isplainobject';

/**
 * Merge BEMJSON-object with tag/mods/mix props
 * delivered from component instance creation.
 *
 * @param {Object} json
 * @param {Object} props
 * @return {Object}
 */
export default function(json, props = {}) {
    json.block = props.block || json.block;
    json.elem = props.elem || json.elem;
    json.tag = props.tag || json.tag;

    json.mods = Object.assign({}, json.mods, props.mods);

    if (props.mix) {
        json.mix = json.mix || [];

        // `mix` as object
        if (isPlainObject(json.mix)) {
            json.mix = [ json.mix ];
        }

        json.mix = json.mix.concat(props.mix);
    }

    return json;
};
<|endoftext|>


<commit_before>'use strict';

var path = require('path');

module.exports = function ext(file, next) {
  file.ext = path.extname(file.path);
  next();
};
<commit_msg>update logic for `ext`<commit_after>'use strict';

var path = require('path');
var utils = require('../utils');

/**
 * Prime the `file` object with properties that
 * can be extended in plugins.
 */

module.exports = function ext_(verb) {
  return function (file, next) {
    file.ext = utils.formatExt(file.ext || path.extname(file.path));
    next();
  }
};
<|endoftext|>


<commit_before>/** internal
 *  class Context
 *
 *  `Context` provides helper methods to all [[EngineTemplate]] processors.
 *  They are typically accessed by EJS templates. You can mix in custom
 *  helpers by injecting them into [[Environment#ContextClass]]. Do not
 *  mix them into `Context` directly.
 *
 *      environment.registerHelper('asset_url', function () {
 *        // ...
 *      });
 *
 *      // or in batch-mode
 *      environment.registerHelper({
 *        asset_url: function () {
 *          // ...
 *        },
 *        // ...
 *      });
 *
 *      <%= asset_url("foo.png") %>
 *
 *  The `Context` also collects dependencies declared by assets.
 *  See [[DirectiveProcessor]] for an example of this.
 **/

'use strict';

// stdlib
var fs        = require('fs');
var path      = require('path');
var inherits  = require('util').inherits;

// 3rd-party
var _ = require('underscore');
var async = require('async');

// internal
var getter = require('./common').getter;
var prop   = require('./common').prop;

////////////////////////////////////////////////////////////////////////////////

/**
 *  new Context(environment, logicalPath, pathname)
 *  - environment (Environment)
 *  - logicalPath (String)
 *  - pathname (String)
 **/
var Context = module.exports = function Context(environment, logicalPath, pathname) {
  prop(this, 'environment',           environment);
  prop(this, 'pathname',              pathname);

  prop(this, '__logicalPath__',       logicalPath);

  prop(this, '__requiredPaths__',     []);
  prop(this, '__stubbedAssets__',     []);
  prop(this, '__dependencyPaths__',   []);
  prop(this, '__dependencyAssets__',  [pathname]);

  prop(this, '__LINE__',              null, {writable: true});
};

// Hash of helpers available as locals in the renderers.
// For each helper, a wrpper will be generated to preserve `this` context.
//
// See Context.registerHelpers()
prop(Context.prototype, '__helpers__', {asset_data_uri: 'assetDataUri'});

/**
 *  Context#rootPath -> String
 *
 *  Returns the environment path that contains the file.
 *
 *  If `app/javascripts` and `app/stylesheets` are in your path, and
 *  current file is `app/javascripts/foo/bar.js`, `root_path` would
 *  return `app/javascripts`.
 **/
getter(Context.prototype, 'rootPath', function () {
  var pathname = this.pathname;
  return _.find(this.environment.paths, function (path) {
    return pathname.substr(0, path.length) === path;
  }).substr(this.environment.root.length + 1);
});

/**
 * Context#logicalPath -> String
 *
 *  Returns logical path without any file extensions.
 *
 *      'app/javascripts/application.js'
 *      # => 'application'
 **/
getter(Context.prototype, 'logicalPath', function () {
  var len = this.__logicalPath__.length, ext = path.extname(this.__logicalPath__);
  return this.__logicalPath__.substr(0, len - ext.length);
});

/**
 *  Context#contentType -> String
 *
 *  Returns content type of file
 *
 *      'application/javascript'
 *      'text/css'
 **/
getter(Context.prototype, 'contentType', function () {
  return this.environment.contentTypeOf(this.pathname);
});

/**
 *  Context#resolve(pathname[, options = {}[, fn]]) -> String
 *  - pathname (String)
 *  - options (Object)
 *  - fn (Function)
 *
 *  Given a logical path, `resolve` will find and return the fully
 *  expanded path. Relative paths will also be resolved. An optional
 *  `contentType` restriction can be supplied to restrict the
 *  search.
 *
 *      context.resolve("foo.js")
 *      # => "/path/to/app/javascripts/foo.js"
 *
 *      context.resolve("./bar.js")
 *      # => "/path/to/app/javascripts/bar.js"
 *
 *      context.resolve("foo", {contentType: 'application/javascript'})
 *      # => "/path/to/app/javascripts/foo.js"
 *
 *  You may also provide an iterator function `fn`, that wil be passed to
 *  [[Environment#resolve]] when needed.
 **/
Context.prototype.resolve = function (pathname, options, fn) {
  var self = this, attributes, content_type, resolved;

  options = options || {};

  if ('/' === pathname[0]) {
    return pathname;
  }

  content_type = options.contentType;

  if (content_type) {
    if ('self' === options.contentType) {
      content_type = self.contentType;
    }

    attributes = this.environment.attributesFor(pathname);
    if (attributes.formatExtension && content_type !== attributes.contentType) {
      throw new Error(pathname + " is '" + attributes.contentType + "', " +
                      "not '" + content_type + "'");
    }

    resolved = this.resolve(pathname, {}, function (candidate) {
      if (self.contentType === self.environment.contentTypeOf(candidate)) {
        return candidate;
      }
    });

    if (!resolved) {
      throw new Error("Could not find file '" + pathname + "'.");
    }

    return resolved;
  }

  return this.environment.resolve(pathname, _.extend({
    basePath: path.dirname(this.pathname)
  }, options), fn);
};

/**
 *  Context#dependOn(pathname) -> Void
 *
 *  Allows you to state a dependency on a file without including it.
 *
 *  This is used for caching purposes. Any changes made to
 *  the dependency file with invalidate the cache of the
 *  source file.
 **/
Context.prototype.dependOn = function (pathname) {
  this.__dependencyPaths__.push(this.resolve(pathname));
};

/**
 *  Context#dependOnAsset(pathname) -> Void
 *
 *  Allows you to state an asset dependency without including it.
 *
 *  This is used for caching purposes. Any changes that would
 *  invalidate the dependency asset will invalidate the source
 *  file. Unlike `depend_on`, this will include recursively include
 *  the target asset's dependencies.
 **/
Context.prototype.dependOnAsset = function (pathname) {
  this.__dependencyAssets__.push(this.resolve(pathname));
};

/**
 *  Context#requireAsset(pathname) -> Void
 *
 *  `require_asset` declares `path` as a dependency of the file. The
 *  dependency will be inserted before the file and will only be
 *  included once.
 *
 *  If EJS processing is enabled, you can use it to dynamically
 *  require assets.
 *
 *      <%= requireAsset("#{framework}.js") %>
 **/
Context.prototype.requireAsset = function (pathname) {
  pathname = this.resolve(pathname, {contentType: 'self'});
  this.dependOnAsset(pathname);
  this.__requiredPaths__.push(pathname);
};

/**
 *  Context#stubAsset(pathname) -> Void
 *
 *  `stubAsset` blacklists `pathname` from being included in the bundle.
 *  `pathname` must be an asset which may or may not already be included
 *  in the bundle.
 **/
Context.prototype.stubAsset = function (pathname) {
  this.__stubbedAssets__.push(this.resolve(pathname, {contentType: 'self'}));
};

/**
 *  Context#isAssetRequirable(pathname) -> Boolean
 *
 *  Tests if target path is able to be safely required into the
 *  current concatenation.
 **/
Context.prototype.isAssetRequirable = function (pathname) {
  var content_type, stat;

  pathname      = this.resolve(pathname);
  content_type  = this.environment.contentTypeOf(pathname);
  stat          = this.environment.stat(pathname);

  return stat && stat.isFile() &&
         (!this.contentType || this.contentType === content_type);
};

/**
 *  Context#assetDataUri(pathname) -> String
 *
 *  Returns a Base64-encoded `data:` URI with the contents of the
 *  asset at the specified path, and marks that path as a dependency
 *  of the current file.
 *
 *  Use `assetDataUri` from EJS with CSS or JavaScript assets:
 *
 *      #logo { background: url(<%= asset_data_uri('logo.png') %>) }
 *
 *      $('<img>').attr('src', '<%= asset_data_uri('avatar.jpg') %>')
 **/
Context.prototype.assetDataUri = function (pathname) {
  var asset = this.environment.findAsset(pathname),
      buff  = asset.buffer || new Buffer(asset.toString());

  this.dependOn(pathname);
  return "data:" + asset.contentType + ";base64," + buff.toString('base64');
};

/**
 *  Context#evaluate(pathname, options = {}, callback) -> Void
 *  - pathname (String)
 *  - options (Object)
 *  - callback (Function)
 *
 *  Reads `pathname` and runs processors on the file.
 **/
Context.prototype.evaluate = function (pathname, options, callback) {
  var self = this, attributes, processors, result, locals = {};

  options     = options || {};
  pathname    = this.resolve(pathname);
  attributes  = this.environment.attributesFor(pathname);
  processors  = options.processors || attributes.processors;
  result      = !!options.data ? options.data
              : fs.readFileSync(pathname, 'utf8');

  _.each(this.__helpers__, function (func, name) {
    locals[name] = function () {
      return self[func].apply(self, arguments);
    };
  });

  async.forEachSeries(processors, function (ProcessorKlass, next) {
    var template = new ProcessorKlass(pathname, function () {
      return result;
    });

    template.evaluate(self, locals, function (err, data) {
      result = data;
      next(err);
    });
  }, function (err) {
    callback(err, result);
  });
};

// internal helper to define registerHelper() method on destination class
function define_helpers_registrator(Klass) {
  Klass.registerHelper = function (name, func) {
    // Scenario: registerHelper('foo', foo_helper);
    if (_.isString(name)) {
      Klass.prototype[name]             = func;
      Klass.prototype.__helpers__[name] = name;
      return;
    }

    // Scenario: registerHelper({ foo: foo_helper, ... });
    _.each(name, function (func, name) {
      Klass.registerHelper(name, func);
    });
  };
}

/**
 *  Context.registerHelper(name, func) -> Void
 *  Context.registerHelper(helpers) -> Void
 *  - name (String)
 *  - func (Function)
 *  - helpers (Object)
 *
 *  Register a helper that will be available in the engines that supports
 *  local helpers (e.g. EJS or Stylus). You should avoid registering helpers
 *  directly on `Context` class in favour of [[Environment#ContextClass]]
 *  (see [[Environment#registerHelper]] as well).
 *
 *  ##### Example
 *
 *      Context.registerHelper('foo', foo_helper);
 *      Context.registerHelper('bar', bar_helper);
 *
 *      // equals to
 *
 *      Context.registerHelper({
 *        foo: foo_helper,
 *        bar: bar_helper
 *      });
 **/
define_helpers_registrator(Context);

/** internal
 *  Context#subclass -> Function
 *
 *  Returns new subclass of [[Context]].
 **/
getter(Context, 'subclass', function () {
  var Klass = function () { Context.apply(this, arguments); };

  // add class inheritance
  inherits(Klass, Context);

  // clone some own-prototype properties
  prop(Klass.prototype, '__helpers__', _.clone(Context.prototype.__helpers__));

  // Provide helpers registrator
  define_helpers_registrator(Klass);

  return Klass;
});
<commit_msg>Fix Context api docs<commit_after>/** internal
 *  class Context
 *
 *  `Context` provides helper methods to all [[EngineTemplate]] processors.
 *  They are typically accessed by EJS templates. You can mix in custom
 *  helpers by injecting them into [[Environment#ContextClass]]. Do not
 *  mix them into `Context` directly.
 *
 *      environment.registerHelper('asset_url', function () {
 *        // ...
 *      });
 *
 *      // or in batch-mode
 *      environment.registerHelper({
 *        asset_url: function () {
 *          // ...
 *        },
 *        // ...
 *      });
 *
 *      <%= asset_url("foo.png") %>
 *
 *  The `Context` also collects dependencies declared by assets.
 *  See [[DirectiveProcessor]] for an example of this.
 **/

'use strict';

// stdlib
var fs        = require('fs');
var path      = require('path');
var inherits  = require('util').inherits;

// 3rd-party
var _ = require('underscore');
var async = require('async');

// internal
var getter = require('./common').getter;
var prop   = require('./common').prop;

////////////////////////////////////////////////////////////////////////////////

/**
 *  new Context(environment, logicalPath, pathname)
 *  - environment (Environment)
 *  - logicalPath (String)
 *  - pathname (String)
 **/
var Context = module.exports = function Context(environment, logicalPath, pathname) {
  prop(this, 'environment',           environment);
  prop(this, 'pathname',              pathname);

  prop(this, '__logicalPath__',       logicalPath);

  prop(this, '__requiredPaths__',     []);
  prop(this, '__stubbedAssets__',     []);
  prop(this, '__dependencyPaths__',   []);
  prop(this, '__dependencyAssets__',  [pathname]);

  prop(this, '__LINE__',              null, {writable: true});
};

// Hash of helpers available as locals in the renderers.
// For each helper, a wrpper will be generated to preserve `this` context.
//
// See Context.registerHelpers()
prop(Context.prototype, '__helpers__', {asset_data_uri: 'assetDataUri'});

/**
 *  Context#rootPath -> String
 *
 *  Returns the environment path that contains the file.
 *
 *  If `app/javascripts` and `app/stylesheets` are in your path, and
 *  current file is `app/javascripts/foo/bar.js`, `root_path` would
 *  return `app/javascripts`.
 **/
getter(Context.prototype, 'rootPath', function () {
  var pathname = this.pathname;
  return _.find(this.environment.paths, function (path) {
    return pathname.substr(0, path.length) === path;
  }).substr(this.environment.root.length + 1);
});

/**
 * Context#logicalPath -> String
 *
 *  Returns logical path without any file extensions.
 *
 *      'app/javascripts/application.js'
 *      # => 'application'
 **/
getter(Context.prototype, 'logicalPath', function () {
  var len = this.__logicalPath__.length, ext = path.extname(this.__logicalPath__);
  return this.__logicalPath__.substr(0, len - ext.length);
});

/**
 *  Context#contentType -> String
 *
 *  Returns content type of file
 *
 *      'application/javascript'
 *      'text/css'
 **/
getter(Context.prototype, 'contentType', function () {
  return this.environment.contentTypeOf(this.pathname);
});

/**
 *  Context#resolve(pathname[, options = {}[, fn]]) -> String
 *  - pathname (String)
 *  - options (Object)
 *  - fn (Function)
 *
 *  Given a logical path, `resolve` will find and return the fully
 *  expanded path. Relative paths will also be resolved. An optional
 *  `contentType` restriction can be supplied to restrict the
 *  search.
 *
 *      context.resolve("foo.js")
 *      # => "/path/to/app/javascripts/foo.js"
 *
 *      context.resolve("./bar.js")
 *      # => "/path/to/app/javascripts/bar.js"
 *
 *      context.resolve("foo", {contentType: 'application/javascript'})
 *      # => "/path/to/app/javascripts/foo.js"
 *
 *  You may also provide an iterator function `fn`, that wil be passed to
 *  [[Environment#resolve]] when needed.
 **/
Context.prototype.resolve = function (pathname, options, fn) {
  var self = this, attributes, content_type, resolved;

  options = options || {};

  if ('/' === pathname[0]) {
    return pathname;
  }

  content_type = options.contentType;

  if (content_type) {
    if ('self' === options.contentType) {
      content_type = self.contentType;
    }

    attributes = this.environment.attributesFor(pathname);
    if (attributes.formatExtension && content_type !== attributes.contentType) {
      throw new Error(pathname + " is '" + attributes.contentType + "', " +
                      "not '" + content_type + "'");
    }

    resolved = this.resolve(pathname, {}, function (candidate) {
      if (self.contentType === self.environment.contentTypeOf(candidate)) {
        return candidate;
      }
    });

    if (!resolved) {
      throw new Error("Could not find file '" + pathname + "'.");
    }

    return resolved;
  }

  return this.environment.resolve(pathname, _.extend({
    basePath: path.dirname(this.pathname)
  }, options), fn);
};

/**
 *  Context#dependOn(pathname) -> Void
 *
 *  Allows you to state a dependency on a file without including it.
 *
 *  This is used for caching purposes. Any changes made to
 *  the dependency file with invalidate the cache of the
 *  source file.
 **/
Context.prototype.dependOn = function (pathname) {
  this.__dependencyPaths__.push(this.resolve(pathname));
};

/**
 *  Context#dependOnAsset(pathname) -> Void
 *
 *  Allows you to state an asset dependency without including it.
 *
 *  This is used for caching purposes. Any changes that would
 *  invalidate the dependency asset will invalidate the source
 *  file. Unlike [[Context#dependOn]], this will include recursively
 *  the target asset's dependencies.
 **/
Context.prototype.dependOnAsset = function (pathname) {
  this.__dependencyAssets__.push(this.resolve(pathname));
};

/**
 *  Context#requireAsset(pathname) -> Void
 *
 *  `require_asset` declares `path` as a dependency of the file. The
 *  dependency will be inserted before the file and will only be
 *  included once.
 *
 *  If EJS processing is enabled, you can use it to dynamically
 *  require assets.
 *
 *      <%= requireAsset("#{framework}.js") %>
 **/
Context.prototype.requireAsset = function (pathname) {
  pathname = this.resolve(pathname, {contentType: 'self'});
  this.dependOnAsset(pathname);
  this.__requiredPaths__.push(pathname);
};

/**
 *  Context#stubAsset(pathname) -> Void
 *
 *  `stubAsset` blacklists `pathname` from being included in the bundle.
 *  `pathname` must be an asset which may or may not already be included
 *  in the bundle.
 **/
Context.prototype.stubAsset = function (pathname) {
  this.__stubbedAssets__.push(this.resolve(pathname, {contentType: 'self'}));
};

/**
 *  Context#isAssetRequirable(pathname) -> Boolean
 *
 *  Tests if target path is able to be safely required into the
 *  current concatenation.
 **/
Context.prototype.isAssetRequirable = function (pathname) {
  var content_type, stat;

  pathname      = this.resolve(pathname);
  content_type  = this.environment.contentTypeOf(pathname);
  stat          = this.environment.stat(pathname);

  return stat && stat.isFile() &&
         (!this.contentType || this.contentType === content_type);
};

/**
 *  Context#assetDataUri(pathname) -> String
 *
 *  Returns a Base64-encoded `data:` URI with the contents of the
 *  asset at the specified path, and marks that path as a dependency
 *  of the current file.
 *
 *  Use `assetDataUri` from EJS with CSS or JavaScript assets:
 *
 *      #logo { background: url(<%= asset_data_uri('logo.png') %>) }
 *
 *      $('<img>').attr('src', '<%= asset_data_uri('avatar.jpg') %>')
 **/
Context.prototype.assetDataUri = function (pathname) {
  var asset = this.environment.findAsset(pathname),
      buff  = asset.buffer || new Buffer(asset.toString());

  this.dependOn(pathname);
  return "data:" + asset.contentType + ";base64," + buff.toString('base64');
};

/**
 *  Context#evaluate(pathname, options = {}, callback) -> Void
 *  - pathname (String)
 *  - options (Object)
 *  - callback (Function)
 *
 *  Reads `pathname` and runs processors on the file.
 **/
Context.prototype.evaluate = function (pathname, options, callback) {
  var self = this, attributes, processors, result, locals = {};

  options     = options || {};
  pathname    = this.resolve(pathname);
  attributes  = this.environment.attributesFor(pathname);
  processors  = options.processors || attributes.processors;
  result      = !!options.data ? options.data
              : fs.readFileSync(pathname, 'utf8');

  _.each(this.__helpers__, function (func, name) {
    locals[name] = function () {
      return self[func].apply(self, arguments);
    };
  });

  async.forEachSeries(processors, function (ProcessorKlass, next) {
    var template = new ProcessorKlass(pathname, function () {
      return result;
    });

    template.evaluate(self, locals, function (err, data) {
      result = data;
      next(err);
    });
  }, function (err) {
    callback(err, result);
  });
};

// internal helper to define registerHelper() method on destination class
function define_helpers_registrator(Klass) {
  Klass.registerHelper = function (name, func) {
    // Scenario: registerHelper('foo', foo_helper);
    if (_.isString(name)) {
      Klass.prototype[name]             = func;
      Klass.prototype.__helpers__[name] = name;
      return;
    }

    // Scenario: registerHelper({ foo: foo_helper, ... });
    _.each(name, function (func, name) {
      Klass.registerHelper(name, func);
    });
  };
}

/**
 *  Context.registerHelper(name, func) -> Void
 *  Context.registerHelper(helpers) -> Void
 *  - name (String)
 *  - func (Function)
 *  - helpers (Object)
 *
 *  Register a helper that will be available in the engines that supports
 *  local helpers (e.g. EJS or Stylus). You should avoid registering helpers
 *  directly on `Context` class in favour of [[Environment#ContextClass]]
 *  (see [[Environment#registerHelper]] as well).
 *
 *  ##### Example
 *
 *      Context.registerHelper('foo', foo_helper);
 *      Context.registerHelper('bar', bar_helper);
 *
 *      // equals to
 *
 *      Context.registerHelper({
 *        foo: foo_helper,
 *        bar: bar_helper
 *      });
 **/
define_helpers_registrator(Context);

/** internal
 *  Context#subclass -> Function
 *
 *  Returns new subclass of [[Context]].
 **/
getter(Context, 'subclass', function () {
  var Klass = function () { Context.apply(this, arguments); };

  // add class inheritance
  inherits(Klass, Context);

  // clone some own-prototype properties
  prop(Klass.prototype, '__helpers__', _.clone(Context.prototype.__helpers__));

  // Provide helpers registrator
  define_helpers_registrator(Klass);

  return Klass;
});
<|endoftext|>