<commit_before>$(document).ready(function() {
    'use strict';

    var list = $('#list-content');

    var students = {};
    var groups = {};

    var acValToId = {'group' : {}};
    var displayStudent = {};

    var sort = {
        $sorter: $('.sorter').first(),
        field: 'idStudent',
        ascending: true,

        update: function(sorter) {
            var newField = $(sorter).data('sort');

            if (this.field === newField) {
                var icon = this.$sorter.find('i');

                this.ascending = !this.ascending;
                icon.text(this._getIcon());

                displayStudent.sort(this._getSortFunction());
            }
            else {
                this.ascending = true;
                this.field = newField;
                displayStudent.sort(this._compare);

                this.$sorter.find('i').addClass('scale-out');

                this.$sorter = $(sorter);
                this.$sorter.find('i').removeClass('scale-out');
            }
        },
        _compare: function(el1, el2) {
            return el1[sort.field] > el2[sort.field] ? 1 : -1;
        },
        _compareInvert: function(el1, el2) {
            return el2[sort.field] >= el1[sort.field] ? 1 : -1;
        },
        _getSortFunction: function() {
            return this.ascending ? this._compare : this._compareInvert;
        },
        _getIcon: function() {
            return this.ascending ? 'keyboard_arrow_down' : 'keyboard_arrow_up';
        }
    };

    $.post(
        '/Process_Student/get_all', {},
        function (resources) {
            students = resources.students;
            displayStudent = students;
            groups = resources.groups;

            $('#list-progress').remove();

            if (students.length) {
                var autocompleteDatas = {};

                $.each(students, function(index, student) {
                    autocompleteDatas[student.idStudent + ' ' + student.surname + ' ' + student.name] = null;
                    list.append(
                        $('')
                            .addClass('black-text')
                            .append(
                                $('

')
                                    .append('
' + student.idStudent + '
')
                                    .append('
' + student.surname + '
')
                                    .append('
' + student.name + '
')
                            )
                    )
                });

                $.each(groups, function(index, group) {
                    var val = group.groupName + ' ' + group.schoolYear + '-' + (parseInt(group.schoolYear)+1);
                    acValToId.group[val] = group.idGroup;
                    autocompleteDatas[val] = null;
                });

                $('#search').autocomplete({
                    data: autocompleteDatas,
                    limit: 15,
                    onAutocomplete: function(val) {
                        var fword = val.split(' ')[0];

                        // If student
                        if (/^p\d{7}$/.test(fword)) {
                            location.href = '/Student/profile/' + fword;
                        }
                        // If group
                        else if (/^G\d+S[0-4]$/.test(fword)) {
                            displayStudent = groups[acValToId.group[val]].students;
                            updateList();
                        }
                    }
                });

                $('#list-student').on('click', '.sorter', function() {
                    sort.update(this);
                    updateList();
                });
            } else {
                list.append('
Pas d\'étudiant à afficher
')
            }
        }
    );

    function updateList() {
        list.empty();
        $.each(displayStudent, function(index, student) {
            list.append(
                $('')
                    .addClass('black-text')
                    .append(
                        $('

')
                            .append('
' + student.idStudent + '
')
                            .append('
' + student.surname + '
')
                            .append('
' + student.name + '
')
                    )
            )
        });
    }
});
<commit_msg>Affiche tous les étudiants si champ de recherche vide<commit_after>$(document).ready(function() {
    'use strict';

    var list = $('#list-content');

    var students = {};
    var groups = {};

    var acValToId = {'group' : {}};
    var displayStudent = {};

    var sort = {
        $sorter: $('.sorter').first(),
        _field: 'idStudent',
        _ascending: true,

        init: function() {
            this._field = 'idStudent';
            this._ascending = true;

            this.$sorter.find('i').addClass('scale-out');

            this.$sorter = $('.sorter').first();
            this.$sorter.find('i')
                .text(this._getIcon())
                .removeClass('scale-out');
        },
        update: function(sorter) {
            var newField = $(sorter).data('sort');

            if (this._field === newField) {
                var icon = this.$sorter.find('i');

                this._ascending = !this._ascending;
                icon.text(this._getIcon());

                displayStudent.sort(this._getSortFunction());
            }
            else {
                this._ascending = true;
                this._field = newField;
                displayStudent.sort(this._compare);

                this.$sorter.find('i').addClass('scale-out');

                this.$sorter = $(sorter);
                this.$sorter.find('i').removeClass('scale-out');
            }
        },
        _compare: function(el1, el2) {
            return el1[sort._field] > el2[sort._field] ? 1 : -1;
        },
        _compareInvert: function(el1, el2) {
            return el2[sort._field] >= el1[sort._field] ? 1 : -1;
        },
        _getSortFunction: function() {
            return this._ascending ? this._compare : this._compareInvert;
        },
        _getIcon: function() {
            return this._ascending ? 'keyboard_arrow_down' : 'keyboard_arrow_up';
        }
    };

    $.post(
        '/Process_Student/get_all', {},
        function (resources) {
            students = resources.students;
            displayStudent = students;
            groups = resources.groups;

            $('#list-progress').remove();

            if (students.length) {
                var autocompleteDatas = {};

                $.each(students, function(index, student) {
                    autocompleteDatas[student.idStudent + ' ' + student.surname + ' ' + student.name] = null;
                    list.append(
                        $('')
                            .addClass('black-text')
                            .append(
                                $('

')
                                    .append('
' + student.idStudent + '
')
                                    .append('
' + student.surname + '
')
                                    .append('
' + student.name + '
')
                            )
                    )
                });

                $.each(groups, function(index, group) {
                    var val = group.groupName + ' ' + group.schoolYear + '-' + (parseInt(group.schoolYear)+1);
                    acValToId.group[val] = group.idGroup;
                    autocompleteDatas[val] = null;
                });

                $('#search')
                    .on('keydown', function(event) {
                        if (event.keyCode === 8 && this.value) {
                            this.value = '';
                            displayStudent = students;
                            sort.init();
                            updateList();
                        }
                    })
                    .autocomplete({
                    data: autocompleteDatas,
                    limit: 15,
                    onAutocomplete: function(val) {
                        var fword = val.split(' ')[0];

                        // If student
                        if (/^p\d{7}$/.test(fword)) {
                            location.href = '/Student/profile/' + fword;
                        }
                        // If group
                        else if (/^G\d+S[0-4]$/.test(fword)) {
                            displayStudent = groups[acValToId.group[val]].students;
                            updateList();
                        }
                    }
                });

                $('#list-student').on('click', '.sorter', function() {
                    sort.update(this);
                    updateList();
                });
            } else {
                list.append('
Pas d\'étudiant à afficher
')
            }
        }
    );

    function updateList() {
        list.empty();
        $.each(displayStudent, function(index, student) {
            list.append(
                $('')
                    .addClass('black-text')
                    .append(
                        $('

')
                            .append('
' + student.idStudent + '
')
                            .append('
' + student.surname + '
')
                            .append('
' + student.name + '
')
                    )
            )
        });
    }
});
<|endoftext|>


<commit_before>/**
 * Utility function related to checking if a given value is numeric.
 *
 * Site Kit by Google, Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Checks if a given value is numeric.
 *
 * @since n.e.x.t
 *
 * @param {*} value The value to check.
 * @return {boolean} TRUE if a value is numeric FALSE otherwise.
 */
export function isNumeric( value ) {
	if ( typeof value === 'number' ) {
		return true;
	}
	const string = ( value || '' ).toString();
	if ( ! string ) {
		return false;
	}
	return ! isNaN( string );
}
<commit_msg>Add spacing to isNumeric function.<commit_after>/**
 * Utility function related to checking if a given value is numeric.
 *
 * Site Kit by Google, Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Checks if a given value is numeric.
 *
 * @since n.e.x.t
 *
 * @param {*} value The value to check.
 * @return {boolean} TRUE if a value is numeric FALSE otherwise.
 */
export function isNumeric( value ) {
	if ( typeof value === 'number' ) {
		return true;
	}

	const string = ( value || '' ).toString();

	if ( ! string ) {
		return false;
	}

	return ! isNaN( string );
}
<|endoftext|>


<commit_before>// Global variables
var currentLayer = 0;
var layerList = new Array();
var stage = undefined;
var connection = new WebSocket('ws://localhost:8080');
var open = false;

function parseServerMessage(message) {

    var now = new Date(),
        now = now.getHours()+':'+now.getMinutes()+':'+now.getSeconds();
    $("#debug").prepend("
["+now+"] Drawing "+message.type+"
")

    layerList[currentLayer] = new Kinetic.Layer();
    var figure;

    switch (message.type) {
        case "line":
            figure = drawLine(message.start, message.end);
            break;
        case "polygon":
            figure = drawPolygon();
            break;
        case "circle":
            figure = drawCircle(message.data.x, message.data.y, message.data.radius);
            break;
        default:
            window.alert("Unrecognized JSON message received from server.");
            figure = null;
    }
    layerList[currentLayer].add(figure);
    stage.add(layerList[currentLayer]);
    layerList[currentLayer].on('click', function(event) {
        window.alert("Clicked on " + event.targetNode.getClassName() + " on layer " + layerList.indexOf(event.targetNode.getLayer()));
    });
    currentLayer++;
}

function drawLine(begin, end) {
    figure = new Kinetic.Line({
        points: [begin, end],
        stroke: "blue",
        strokeWidth: 8
    });
    return figure;
}

function drawPolygon() {
    figure = new Kinetic.Polygon({
        points: [73, 192, 73, 160, 340, 23, 500, 109, 499, 139, 342, 93],
        fill: '#00D2FF',
        stroke: 'black',
        strokeWidth: 1
    });
    return figure;
}

function drawCircle(_x, _y, _radius) {
    figure = new Kinetic.Circle({
        radius: _radius,
        fill: '#FFD200',
        stroke: 'black',
        strokeWidth: 1,
        x: _x,
        y: _y
    });
    return figure;
}

$(document).ready(function() {

    var width = 900; // defined here because the contaier also needs these proportions
    var height = 600;

    $( "#wrapper" ).css( "min-width", width+"px" );
    $( "#wrapper" ).css( "height", $( window ).height()+"px" );

    $( window ).resize(function() {
        $( "#wrapper" ).css( "height", $( window ).height()+"px" );
    });

    $( "#canvas" ).css( "width", width+"px" );
    $( "#canvas" ).css( "height", height+"px" );
    $( "#canvas" ).css( "margin-top", "-"+height/2+"px" );
    $( "#canvas" ).css( "margin-left", "-"+(width + $( "#debug" ).width())/2+"px" );

    stage = new Kinetic.Stage({
        container: 'canvas',
        width: width,
        height: height
    });

    // When the connection is open, send some data to the server
    connection.onopen = function () {
    };

    // Log errors
    connection.onerror = function (error) {
      console.log('WebSocket Error ');
      console.log(error);
    };

    // Log messages from the server
    connection.onmessage = function (e) {
        console.log("test"+e.data);
        parseServerMessage(jQuery.parseJSON(e.data));
    };

    window.setInterval(function(){
        connection.send("");
    }, 2000);

});

<commit_msg>Most basic shapes can be drawn<commit_after>// Global variables
var currentLayer = 0;
var layerList = new Array();
var stage = undefined;
var connection = new WebSocket('ws://localhost:8080');
var open = false;

function parseFigureMessage(message) {
    layerList[currentLayer] = new Kinetic.Layer();
    var figure = makeFigure(message);

    var now = new Date(),
        now = now.getHours()+':'+now.getMinutes()+':'+now.getSeconds();
    $("#debug").prepend("
["+now+"] Drawing "+message.type+"
")

    layerList[currentLayer].add(figure);
    stage.add(layerList[currentLayer]);

    // Click event used for debugging is added below
    layerList[currentLayer].on('click', function(event) {
        window.alert("Clicked on " + event.targetNode.getClassName() + " on layer " + layerList.indexOf(event.targetNode.getLayer()));
    });
    currentLayer++;
}

function makeFigure(message) {
    var figure;
    switch (message.type) {
        case "line":
            figure = drawLine(message.data);
            break;
        case "polygon":
            figure = drawPolygon(message.data);
            break;
        case "circle":
            figure = drawCircle(message.data);
            break;
        case "rect":
            figure = drawRect(message.data);
            break;
        case "text":
            figure = drawText(message.data);
            break;
        case "container":
            figure = drawGroup(message.data);
            message.children.forEach(function(child) {
                figure.add(makeFigure(child));
            });
            break;
        default:
            window.alert("Unrecognized JSON message received from server.");
            figure = null;
    }
    return figure;
}

function drawLine(data) {
    return new Kinetic.Line(data);
}
function drawPolygon(data) {
    return new Kinetic.Polygon(data);
}
function drawCircle(data) {
    return new Kinetic.Circle(data);
}
function drawRect(data) {
    return new Kinetic.Rect(data);
}
function drawText(data) {
    return new Kinetic.Text(data);
}
function drawGroup(data) {
    return new Kinetic.Group(data);
}

$(document).ready(function() {

    var width = 900; // defined here because the contaier also needs these proportions
    var height = 600;

    $( "#wrapper" ).css( "min-width", width+"px" );
    $( "#wrapper" ).css( "height", $( window ).height()+"px" );

    $( window ).resize(function() {
        $( "#wrapper" ).css( "height", $( window ).height()+"px" );
    });

    $( "#canvas" ).css( "width", width+"px" );
    $( "#canvas" ).css( "height", height+"px" );
    $( "#canvas" ).css( "margin-top", "-"+height/2+"px" );
    $( "#canvas" ).css( "margin-left", "-"+(width + $( "#debug" ).width())/2+"px" );

    stage = new Kinetic.Stage({
        container: 'canvas',
        width: width,
        height: height
    });

    // When the connection is open, send some data to the server
    connection.onopen = function () {
    };

    // Log errors
    connection.onerror = function (error) {
      console.log('WebSocket Error ');
      console.log(error);
    };

    // Log messages from the server
    connection.onmessage = function (e) {
        console.log("test"+e.data);
        parseServerMessage(jQuery.parseJSON(e.data));
    };

    window.setInterval(function(){
        connection.send("");
    }, 2000);

//    for(var n = 0; n < message.objects.length; n++) {
//        parseFigureMessage(message.objects[n]);
//    }
});

<|endoftext|>


<commit_before>var http = require('http');
var Dockerode = require('dockerode');
var emptyPort = require('empty-port');
var thunkify = require('thunkify');
var mkdirp = require('mkdirp');
var rimraf = require('rimraf');
var config = require('../config');

module.exports = {
  readyContainer: readyContainer,
  createContainer: createContainer
}

var docker = new Dockerode(config.dockerOptions);
var emptyPort = thunkify(emptyPort);
docker.createContainer = thunkify(docker.createContainer);
mkdirp = thunkify(mkdirp);
rimraf = thunkify(rimraf);

function *startContainer(container) {
  var port = yield emptyPort({});
  var folder = __dirname + '/binds/' + container.id;
  yield mkdirp(folder);
  yield rimraf(folder + '/*');
  container.start = thunkify(container.start);
  yield container.start({
    Binds: [folder + ':/var/run']
  });
  yield waitForPort(folder);
  return port;
}

function *unpauseContainer(container) {
  container.unpause = thunkify(container.unpause);
  yield container.unpause();
  yield waitForPort(__dirname + '/binds/' + container.id);
}

function *waitForPort(folder) {
  var count = 1000;
  while (count-- > 0) {
    try {
      var result = yield get({ socketPath: folder + '/proxy' });
      break;
    } catch (error) {
      ignore(error);
    }
  }
  if (count < 1) {
    throw new Error('Could not connect to container');
  }
}

function get(options) {
  return function getThunk(cb) {
    setTimeout(function getAttempt () {
      var req = http.get(options);
      req.on('error', cb);
      req.on('response', function handleGetResponse(res) {
        res.on('error', cb);
        res.on('data', ignore);
        res.on('end', cb);
      });
    }, 50);
  };
}

function *readyContainer(name) {
  var container = docker.getContainer(name);
  container.inspect = thunkify(container.inspect);
  try {
    var info = yield container.inspect();
  } catch (e) {
    console.error(e);
    return next;
  }
  if (!info.State.Running) {
    try {
      yield startContainer(container);
    } catch (err) {
      console.log('failed to start container:', container.id);
      console.error(err);
      this.body = 'failed to start container';
      return false;
    }
  }
  if (info.State.Paused) {
    yield unpauseContainer(container);
  }
  return container.id;
}

function *createContainer(image) {
  var container = yield docker.createContainer({
    Image: config.imagePrefix + image,
    Hostname: image,
    Tty: true,
    Volumes: {
      '/var/run': {}
    }
  });
  container.inspect = thunkify(container.inspect);
  var info = yield container.inspect();
  return info.Name;
}

function ignore() {}
<commit_msg>removed handling in controller<commit_after>var http = require('http');
var Dockerode = require('dockerode');
var emptyPort = require('empty-port');
var thunkify = require('thunkify');
var mkdirp = require('mkdirp');
var rimraf = require('rimraf');
var config = require('../config');

module.exports = {
  readyContainer: readyContainer,
  createContainer: createContainer
}

var docker = new Dockerode(config.dockerOptions);
var emptyPort = thunkify(emptyPort);
docker.createContainer = thunkify(docker.createContainer);
mkdirp = thunkify(mkdirp);
rimraf = thunkify(rimraf);

function *startContainer(container) {
  var port = yield emptyPort({});
  var folder = __dirname + '/binds/' + container.id;
  yield mkdirp(folder);
  yield rimraf(folder + '/*');
  container.start = thunkify(container.start);
  yield container.start({
    Binds: [folder + ':/var/run']
  });
  yield waitForPort(folder);
  return port;
}

function *unpauseContainer(container) {
  container.unpause = thunkify(container.unpause);
  yield container.unpause();
  yield waitForPort(__dirname + '/binds/' + container.id);
}

function *waitForPort(folder) {
  var count = 1000;
  while (count-- > 0) {
    try {
      var result = yield get({ socketPath: folder + '/proxy' });
      break;
    } catch (error) {
      ignore(error);
    }
  }
  if (count < 1) {
    throw new Error('Could not connect to container');
  }
}

function get(options) {
  return function getThunk(cb) {
    setTimeout(function getAttempt () {
      var req = http.get(options);
      req.on('error', cb);
      req.on('response', function handleGetResponse(res) {
        res.on('error', cb);
        res.on('data', ignore);
        res.on('end', cb);
      });
    }, 50);
  };
}

function *readyContainer(name) {
  var container = docker.getContainer(name);
  container.inspect = thunkify(container.inspect);
  var info = yield container.inspect();
  if (!info.State.Running) {
    yield startContainer(container);
  }
  if (info.State.Paused) {
    yield unpauseContainer(container);
  }
  return container.id;
}

function *createContainer(image) {
  var container = yield docker.createContainer({
    Image: config.imagePrefix + image,
    Hostname: image,
    Tty: true,
    Volumes: {
      '/var/run': {}
    }
  });
  container.inspect = thunkify(container.inspect);
  var info = yield container.inspect();
  return info.Name;
}

function ignore() {}
<|endoftext|>


<commit_before>var loghose = require('docker-loghose')
var Docker = require('dockerode')
var through = require('through2')
var Logsene = require('logsene-js')
var SpmAgent = require('spm-agent')
var Logagent = require('@sematext/logagent')
var fs = require('fs')
var loggers = {}
var getLogseneTokenByContainer = require('./dockerInspect')
var DockerEvents = require('docker-events')
var logsShipped = 0
var httpFailed = 0
var httpRetransmit = 0
var httpSend = 0

function getLogger (token) {
  var key = token
  // console.log(token)
  if (!loggers[key]) {
    var logger = new Logsene(token, 'docker')
    logger.on('log', function (res) {
      logsShipped += res.count
      httpSend += 1
    })
    logger.on('error', function (err) {
      httpFailed++
      spmLogError(err)
    })

    logger.on('rt', function (res) {
      httpRetransmit++
    })
    loggers[key] = logger
  }
  return loggers[key]
}
var dockerInspectCache = {}

function logToLogsene (level, message, data) {
  if (process.env.SPM_REPORTED_HOSTNAME) {
    data.host = process.env.SPM_REPORTED_HOSTNAME
  }
  var dockerInspect = dockerInspectCache[data.container_id]
  if (dockerInspect) {
    var logger = getLogger(dockerInspect.LOGSENE_TOKEN || process.env.LOGSENE_TOKEN)
    if (dockerInspect.Config && dockerInspect.Config.Hostname) {
      data.container_hostname = dockerInspect.Config.Hostname
    }
    if (dockerInspect.NetworkSettings && dockerInspect.NetworkSettings.IPAddress) {
      data.ip = dockerInspect.NetworkSettings.IPAddress
    }
    if (dockerInspect.Config && dockerInspect.Config.Labels && dockerInspect.Config.Labels['com.docker.compose.project']) {
      data.compose_project = dockerInspect.Config.Labels['com.docker.compose.project']
      data.compose_container_number = dockerInspect.Config.Labels['com.docker.compose.container-number']
      data.compose_service = dockerInspect.Config.Labels['com.docker.compose.service']
      if (dockerInspect.Config.Labels['com.docker.swarm.id']) {
        data.swarm_id = dockerInspect.Config.Labels['com.docker.swarm.id']
      }
    }
    logger.log(level, message, data)
  } else {
    getLogseneTokenByContainer(data.container_id, function (err, info) {
      var token = process.env.LOGSENE_TOKEN
      if (!err && info) {
        dockerInspectCache[data.container_id] = info
        if (info.LOGSENE_TOKEN) {
          token = info.LOGSENE_TOKEN
        }
      }
      var logger = getLogger(token, 'docker')
      data.logType = data._type || 'docker'
      data._type = 'docker'
      logger.log(level, message, data)
    })
  }
}

function createLogAgent () {
  var patternFile = '/etc/logagent/patterns.yml'
  if (fs.existsSync(patternFile)) {
    console.log(new Date().toISOString() + ' - INFO - Loading custom log parser definitions: ' + patternFile)
    return new Logagent(patternFile)
  } else {
    console.log(new Date().toISOString() + ' - INFO - Use -v /mypattern/patterns.yml:' + patternFile + ' for custom log parser definitions.')
    return new Logagent() // use default patterns
  }
}

function spmLogError (err) {
  SpmAgent.Logger.log('error', 'Error in logsene-js: ', err)
}
function logVersionInfo () {
  var versions = {
    nodeJS: process.versions.node,
    sematextDockerAgent: require('../package.json').version,
    logseneJS: require('logsene-js/package.json').version,
    dockerLoghose: require('docker-loghose/package.json').version,
    dockerStats: require('docker-stats/package.json').version,
    dockerEvents: require('docker-events/package.json').version
  }
  SpmAgent.Logger.info('module versions:' + JSON.stringify(versions, null, '\t'))
}
function DockerLogsene () {
  logVersionInfo()
  if (!process.env.REMOVE_ANSI_ESCAPE_SEQ) {
    process.env.REMOVE_ANSI_ESCAPE_SEQ = 'enabled'
  }
  this.logCount = 0
  this.imageNameRegEx = /sematext\/sematext-agent-docker/
  this.logagent = createLogAgent()
  this.logger = new Logsene(process.env.LOGSENE_TOKEN, 'docker')
  this.logger.on('error', spmLogError)

  var self = this
  if (process.env.DEBUG_SPM_LOGGING && process.env.DEBUG_SPM_LOGGING === 'enabled') {
    this.logger.on('log', function (req) {
      self.debugLog(req.url)
      self.debugLog(req.request)
    })
  }
  this.opts = {
    json: false,
    newline: true,
    docker: null,
    events: null,
    includeCurrentContainer: false,
    // the following options limit the containers being matched
    // so we can avoid catching logs for unwanted containers
    matchByName: process.env.MATCH_BY_NAME,
    matchByImage: process.env.MATCH_BY_IMAGE,
    skipByName: process.env.SKIP_BY_NAME,
    skipByImage: process.env.SKIP_BY_IMAGE
  }
  setTimeout(function () {
    self.initDockerEventHandlers()
  }, 50)
  setTimeout(function () {
    self.connect()
  }, 300)
  if (process.env.ENABLE_LOGSENE_STATS === 'true') {
    setInterval(function () {
      var logInfoString = 'SDA log stats: count=' + self.logCount + ' shipped=' + logsShipped + ' bulks_req_send=' + httpSend + ' bulk_req_failed=' + httpFailed + ' bulk_req_retransmit=' + httpRetransmit
      if (process.env.SPM_LOG_TO_CONSOLE) {
        console.log(logInfoString)
      }
      var logger = getLogger(process.env.LOGSENE_TOKEN, 'sda-logging-stats')
      logger.log('info', logInfoString,
        {type: 'sda-logging-stats',
          sdaStats_logCount: Number(self.logCount + 1),
          sdaStats_logsShipped: Number(logsShipped),
          sdaStats_httpSend: Number(httpSend),
          sdaStats_httpFailed: Number(httpFailed),
        sdaStats_httpRetransmit: Number(httpRetransmit)})
      self.resetStats()
    }, 60000)
  } else {
    setInterval(function () {
      self.resetStats()
    }, 60000)
  }
}

DockerLogsene.prototype.resetStats = function () {
  this.logCount = 0
  logsShipped = 0
  httpFailed = 0
  httpRetransmit = 0
  httpSend = 0
}

DockerLogsene.prototype.initDockerEventHandlers = function () {
  this.docker = new Docker()
  this.dockerEvents = new DockerEvents({docker: this.docker})
  this.dockerEvents.on('connect', function () {
    this.dockerEvents.on('die', function (dockerEvent) {
      delete dockerInspectCache[dockerEvent.id.substr(0, 12)]
    })
    this.dockerEvents.on('start', function (dockerEvent) {
      getLogseneTokenByContainer(dockerEvent.id.substr(0, 12), function (err, info) {
        if (!err && info) {
          dockerInspectCache[dockerEvent.id.substr(0, 12)] = info
        }
      })
    })
    this.dockerEvents.on('attach', function (dockerEvent) {
      var id = dockerEvent.id.substr(0, 12)
      if (dockerInspectCache[id]) {
        return
      }
      getLogseneTokenByContainer(id, function (err, info) {
        if (!err && info) {
          dockerInspectCache[id] = info
        }
      })
    })
  }.bind(this))
  this.dockerEvents.start()
}

DockerLogsene.prototype.debugLog = function (message) {
  if (process.env.DEBUG_SPM_LOGGING && process.env.DEBUG_SPM_LOGGING === 'enabled') {
    SpmAgent.Logger.log('debug', message)
  }
}
DockerLogsene.prototype.getLogObject = function (line, container, cbf) {
  if (process.env.DEBUG_SPM_LOGGING === 'enabled' && this.imageNameRegEx.test(container)) {
    cbf('Disabled logging for image sematext/sematext-agent-docker to avoid endless logging loop', null)
    return
  }
  // this.debugLog('Logs from Docker API (' + line.splitlength + ' lines) ' + container)
  this.debugLog('Logs from Docker API (content) for : ' + container + '\n' + line)
  var self = this
  if (line && line.length > 0) {
    this.logagent.parseLine(line, container, function (err, parsed) {
      self.debugLog('Log parser result: ' + JSON.stringify(parsed))
      return cbf(err, parsed)
    })
  }
}

DockerLogsene.prototype.connect = function () {
  var self = this
  var options = self.opts
  SpmAgent.Logger.log('debug', 'connect logStream to docker.sock')
  self.logStream = through.obj(function (chunk, enc, cb) {
    try {
      if (!chunk.line) {
        cb()
        return
      }
      setImmediate(function () {
        self.debugLog('Logs from Docker API:' + JSON.stringify(chunk))
        var lines = chunk.line.split('\n')
        lines.forEach(function (line) {
          self.logCount++
          self.logLine(line, chunk)
        })
        cb()
      })
    } catch (ex) {
      console.log(ex)
      cb()
    }
  })
  var lh = loghose(options)
  try {
    lh.on('error', this.reconnect)
    lh.pipe(self.logStream).on('error', this.reconnect)
  } catch (ex) {
    setTimeout(this.reconnect, 1000)
  }
}

DockerLogsene.prototype.parseKubernetesInfo = function (containerName, logObject) {
  if (!process.env.KUBERNETES) {
    return null
  }
  var fields = containerName.split('_')
  if (fields.length >= 4) {
    // example: k8s_sematext-agent.c56a873d_sematext-agent-qo2yf_default_e94b48c5-e63b-11e5-a8ec-0401b386ea01_8898bc93
    if (fields[0] !== 'k8s') {
      return null
    }
    logObject.kubernetes = {}
    logObject.kubernetes.container_name = fields[1]
    logObject.kubernetes.pod_name = fields[2]
    logObject.kubernetes.namespace = fields[3]
    logObject.kubernetes.uid = fields[4]
    return logObject
  } else {
    return null
  }
}

DockerLogsene.prototype.enrichAndLog = function (err, logObject) {
  if (err && !logObject) {
    return
  } else {
    if (!(logObject['@timestamp'] instanceof Date)) {
      logObject['@timestamp'] = new Date()
    }
    logObject.container_id = this.chunk.id
    logObject.image_name = this.chunk.image
    logObject.container_name = this.chunk.name

    // logObject['logSource'] = this.sourceName
    if (!logObject['@timestamp']) {
      logObject['@timestamp'] = new Date(this.chunk.time)
    }
    this.parseKubernetesInfo(this.chunk.name, logObject)

    if ((Date.now() - logObject['@timestamp'].getTime()) < (90 * 24 * 60 * 60 * 1000)) {
      logToLogsene(
        String((logObject.level || logObject.lvl || logObject.severity || 'info')),
        logObject.message || logObject.msg || logObject.MESSAGE,
        logObject)
    }
  }
}
var ansiEscapeRegEx = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g
DockerLogsene.prototype.logLine = function (messageText, data, next) {
  var self = this
  var dockerInspect = dockerInspectCache[data.id]
  if (dockerInspect) {
    if (dockerInspect.LOGSENE_ENABLED === false) {
      return
    }
  }
  var logContext = {
    parseKubernetesInfo: self.parseKubernetesInfo,
    sourceName: data.image + '_' + data.name + '_' + data.id,
    chunk: data,
    line: (process.env.REMOVE_ANSI_ESCAPE_SEQ === 'enabled') ? messageText.replace(ansiEscapeRegEx, '') : messageText
  }
  setImmediate(function () {
    self.getLogObject(logContext.line, logContext.sourceName, self.enrichAndLog.bind(logContext))
  })
}

DockerLogsene.prototype.reconnect = function (err) {
  var self = this
  SpmAgent.Logger.log('error', 'Error in log stream: ' + err)
  try {
    self.logStream = null
    self.connect()
    SpmAgent.Logger.log('debug', 'reconnect to docker.sock ')
  } catch (ex) {
    SpmAgent.Logger.log('error', ex)
    setTimeout(function () {
      self.reconnect()
      SpmAgent.Logger.log('debug', 'reconnect to docker.sock ')
    }, 1000)
  }
}

module.exports = new DockerLogsene()
<commit_msg>don't set _type to image name<commit_after>var loghose = require('docker-loghose')
var Docker = require('dockerode')
var through = require('through2')
var Logsene = require('logsene-js')
var SpmAgent = require('spm-agent')
var Logagent = require('@sematext/logagent')
var fs = require('fs')
var loggers = {}
var getLogseneTokenByContainer = require('./dockerInspect')
var DockerEvents = require('docker-events')
var logsShipped = 0
var httpFailed = 0
var httpRetransmit = 0
var httpSend = 0

function getLogger (token) {
  var key = token
  // console.log(token)
  if (!loggers[key]) {
    var logger = new Logsene(token, 'docker')
    logger.on('log', function (res) {
      logsShipped += res.count
      httpSend += 1
    })
    logger.on('error', function (err) {
      httpFailed++
      spmLogError(err)
    })

    logger.on('rt', function (res) {
      httpRetransmit++
    })
    loggers[key] = logger
  }
  return loggers[key]
}
var dockerInspectCache = {}

function logToLogsene (level, message, data) {
  if (process.env.SPM_REPORTED_HOSTNAME) {
    data.host = process.env.SPM_REPORTED_HOSTNAME
  }
  data.logType = data._type || 'docker'
  data._type = 'docker'
  var dockerInspect = dockerInspectCache[data.container_id]
  if (dockerInspect) {
    var logger = getLogger(dockerInspect.LOGSENE_TOKEN || process.env.LOGSENE_TOKEN)
    if (dockerInspect.Config && dockerInspect.Config.Hostname) {
      data.container_hostname = dockerInspect.Config.Hostname
    }
    if (dockerInspect.NetworkSettings && dockerInspect.NetworkSettings.IPAddress) {
      data.ip = dockerInspect.NetworkSettings.IPAddress
    }
    if (dockerInspect.Config && dockerInspect.Config.Labels && dockerInspect.Config.Labels['com.docker.compose.project']) {
      data.compose_project = dockerInspect.Config.Labels['com.docker.compose.project']
      data.compose_container_number = dockerInspect.Config.Labels['com.docker.compose.container-number']
      data.compose_service = dockerInspect.Config.Labels['com.docker.compose.service']
      if (dockerInspect.Config.Labels['com.docker.swarm.id']) {
        data.swarm_id = dockerInspect.Config.Labels['com.docker.swarm.id']
      }
    }
    logger.log(level, message, data)
  } else {
    getLogseneTokenByContainer(data.container_id, function (err, info) {
      var token = process.env.LOGSENE_TOKEN
      if (!err && info) {
        dockerInspectCache[data.container_id] = info
        if (info.LOGSENE_TOKEN) {
          token = info.LOGSENE_TOKEN
        }
      }
      var logger = getLogger(token, 'docker')
      logger.log(level, message, data)
    })
  }
}

function createLogAgent () {
  var patternFile = '/etc/logagent/patterns.yml'
  if (fs.existsSync(patternFile)) {
    console.log(new Date().toISOString() + ' - INFO - Loading custom log parser definitions: ' + patternFile)
    return new Logagent(patternFile)
  } else {
    console.log(new Date().toISOString() + ' - INFO - Use -v /mypattern/patterns.yml:' + patternFile + ' for custom log parser definitions.')
    return new Logagent() // use default patterns
  }
}

function spmLogError (err) {
  SpmAgent.Logger.log('error', 'Error in logsene-js: ', err)
}
function logVersionInfo () {
  var versions = {
    nodeJS: process.versions.node,
    sematextDockerAgent: require('../package.json').version,
    logseneJS: require('logsene-js/package.json').version,
    dockerLoghose: require('docker-loghose/package.json').version,
    dockerStats: require('docker-stats/package.json').version,
    dockerEvents: require('docker-events/package.json').version
  }
  SpmAgent.Logger.info('module versions:' + JSON.stringify(versions, null, '\t'))
}
function DockerLogsene () {
  logVersionInfo()
  if (!process.env.REMOVE_ANSI_ESCAPE_SEQ) {
    process.env.REMOVE_ANSI_ESCAPE_SEQ = 'enabled'
  }
  this.logCount = 0
  this.imageNameRegEx = /sematext\/sematext-agent-docker/
  this.logagent = createLogAgent()
  this.logger = new Logsene(process.env.LOGSENE_TOKEN, 'docker')
  this.logger.on('error', spmLogError)

  var self = this
  if (process.env.DEBUG_SPM_LOGGING && process.env.DEBUG_SPM_LOGGING === 'enabled') {
    this.logger.on('log', function (req) {
      self.debugLog(req.url)
      self.debugLog(req.request)
    })
  }
  this.opts = {
    json: false,
    newline: true,
    docker: null,
    events: null,
    includeCurrentContainer: false,
    // the following options limit the containers being matched
    // so we can avoid catching logs for unwanted containers
    matchByName: process.env.MATCH_BY_NAME,
    matchByImage: process.env.MATCH_BY_IMAGE,
    skipByName: process.env.SKIP_BY_NAME,
    skipByImage: process.env.SKIP_BY_IMAGE
  }
  setTimeout(function () {
    self.initDockerEventHandlers()
  }, 50)
  setTimeout(function () {
    self.connect()
  }, 300)
  if (process.env.ENABLE_LOGSENE_STATS === 'true') {
    setInterval(function () {
      var logInfoString = 'SDA log stats: count=' + self.logCount + ' shipped=' + logsShipped + ' bulks_req_send=' + httpSend + ' bulk_req_failed=' + httpFailed + ' bulk_req_retransmit=' + httpRetransmit
      if (process.env.SPM_LOG_TO_CONSOLE) {
        console.log(logInfoString)
      }
      var logger = getLogger(process.env.LOGSENE_TOKEN, 'sda-logging-stats')
      logger.log('info', logInfoString,
        {type: 'sda-logging-stats',
          sdaStats_logCount: Number(self.logCount + 1),
          sdaStats_logsShipped: Number(logsShipped),
          sdaStats_httpSend: Number(httpSend),
          sdaStats_httpFailed: Number(httpFailed),
        sdaStats_httpRetransmit: Number(httpRetransmit)})
      self.resetStats()
    }, 60000)
  } else {
    setInterval(function () {
      self.resetStats()
    }, 60000)
  }
}

DockerLogsene.prototype.resetStats = function () {
  this.logCount = 0
  logsShipped = 0
  httpFailed = 0
  httpRetransmit = 0
  httpSend = 0
}

DockerLogsene.prototype.initDockerEventHandlers = function () {
  this.docker = new Docker()
  this.dockerEvents = new DockerEvents({docker: this.docker})
  this.dockerEvents.on('connect', function () {
    this.dockerEvents.on('die', function (dockerEvent) {
      delete dockerInspectCache[dockerEvent.id.substr(0, 12)]
    })
    this.dockerEvents.on('start', function (dockerEvent) {
      getLogseneTokenByContainer(dockerEvent.id.substr(0, 12), function (err, info) {
        if (!err && info) {
          dockerInspectCache[dockerEvent.id.substr(0, 12)] = info
        }
      })
    })
    this.dockerEvents.on('attach', function (dockerEvent) {
      var id = dockerEvent.id.substr(0, 12)
      if (dockerInspectCache[id]) {
        return
      }
      getLogseneTokenByContainer(id, function (err, info) {
        if (!err && info) {
          dockerInspectCache[id] = info
        }
      })
    })
  }.bind(this))
  this.dockerEvents.start()
}

DockerLogsene.prototype.debugLog = function (message) {
  if (process.env.DEBUG_SPM_LOGGING && process.env.DEBUG_SPM_LOGGING === 'enabled') {
    SpmAgent.Logger.log('debug', message)
  }
}
DockerLogsene.prototype.getLogObject = function (line, container, cbf) {
  if (process.env.DEBUG_SPM_LOGGING === 'enabled' && this.imageNameRegEx.test(container)) {
    cbf('Disabled logging for image sematext/sematext-agent-docker to avoid endless logging loop', null)
    return
  }
  // this.debugLog('Logs from Docker API (' + line.splitlength + ' lines) ' + container)
  this.debugLog('Logs from Docker API (content) for : ' + container + '\n' + line)
  var self = this
  if (line && line.length > 0) {
    this.logagent.parseLine(line, container, function (err, parsed) {
      self.debugLog('Log parser result: ' + JSON.stringify(parsed))
      return cbf(err, parsed)
    })
  }
}

DockerLogsene.prototype.connect = function () {
  var self = this
  var options = self.opts
  SpmAgent.Logger.log('debug', 'connect logStream to docker.sock')
  self.logStream = through.obj(function (chunk, enc, cb) {
    try {
      if (!chunk.line) {
        cb()
        return
      }
      setImmediate(function () {
        self.debugLog('Logs from Docker API:' + JSON.stringify(chunk))
        var lines = chunk.line.split('\n')
        lines.forEach(function (line) {
          self.logCount++
          self.logLine(line, chunk)
        })
        cb()
      })
    } catch (ex) {
      console.log(ex)
      cb()
    }
  })
  var lh = loghose(options)
  try {
    lh.on('error', this.reconnect)
    lh.pipe(self.logStream).on('error', this.reconnect)
  } catch (ex) {
    setTimeout(this.reconnect, 1000)
  }
}

DockerLogsene.prototype.parseKubernetesInfo = function (containerName, logObject) {
  if (!process.env.KUBERNETES) {
    return null
  }
  var fields = containerName.split('_')
  if (fields.length >= 4) {
    // example: k8s_sematext-agent.c56a873d_sematext-agent-qo2yf_default_e94b48c5-e63b-11e5-a8ec-0401b386ea01_8898bc93
    if (fields[0] !== 'k8s') {
      return null
    }
    logObject.kubernetes = {}
    logObject.kubernetes.container_name = fields[1]
    logObject.kubernetes.pod_name = fields[2]
    logObject.kubernetes.namespace = fields[3]
    logObject.kubernetes.uid = fields[4]
    return logObject
  } else {
    return null
  }
}

DockerLogsene.prototype.enrichAndLog = function (err, logObject) {
  if (err && !logObject) {
    return
  } else {
    if (!(logObject['@timestamp'] instanceof Date)) {
      logObject['@timestamp'] = new Date()
    }
    logObject.container_id = this.chunk.id
    logObject.image_name = this.chunk.image
    logObject.container_name = this.chunk.name

    // logObject['logSource'] = this.sourceName
    if (!logObject['@timestamp']) {
      logObject['@timestamp'] = new Date(this.chunk.time)
    }
    this.parseKubernetesInfo(this.chunk.name, logObject)

    if ((Date.now() - logObject['@timestamp'].getTime()) < (90 * 24 * 60 * 60 * 1000)) {
      logToLogsene(
        String((logObject.level || logObject.lvl || logObject.severity || 'info')),
        logObject.message || logObject.msg || logObject.MESSAGE,
        logObject)
    }
  }
}
var ansiEscapeRegEx = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g
DockerLogsene.prototype.logLine = function (messageText, data, next) {
  var self = this
  var dockerInspect = dockerInspectCache[data.id]
  if (dockerInspect) {
    if (dockerInspect.LOGSENE_ENABLED === false) {
      return
    }
  }
  var logContext = {
    parseKubernetesInfo: self.parseKubernetesInfo,
    sourceName: data.image + '_' + data.name + '_' + data.id,
    chunk: data,
    line: (process.env.REMOVE_ANSI_ESCAPE_SEQ === 'enabled') ? messageText.replace(ansiEscapeRegEx, '') : messageText
  }
  setImmediate(function () {
    self.getLogObject(logContext.line, logContext.sourceName, self.enrichAndLog.bind(logContext))
  })
}

DockerLogsene.prototype.reconnect = function (err) {
  var self = this
  SpmAgent.Logger.log('error', 'Error in log stream: ' + err)
  try {
    self.logStream = null
    self.connect()
    SpmAgent.Logger.log('debug', 'reconnect to docker.sock ')
  } catch (ex) {
    SpmAgent.Logger.log('error', ex)
    setTimeout(function () {
      self.reconnect()
      SpmAgent.Logger.log('debug', 'reconnect to docker.sock ')
    }, 1000)
  }
}

module.exports = new DockerLogsene()
<|endoftext|>


<commit_before>// for DEBUG
var count = 0;
// the abstract values for concrete values
// which are the sets of types
// We maintain 'props' for performance.
function AVal(type) {
    if (type) this.types = [type];
    else this.types = [];
    // OPTIONAL
    // for abstract values,
    // a prop is the merged AVals each types.
    this.props = Object.create(null);
    // for DEBUG
    this._id = count++;
}

/**
 * @returns {[Type]}
 */
AVal.prototype.getTypes = function () {
    return this.types;
};

/**
 * @returns {boolean}
 */
AVal.prototype.hasType = function (type) {
    return this.types.indexOf(type) > -1;
};

/**
 * @param {Type} type
 */
AVal.prototype.addType = function (type) {
    if (this.types.indexOf(type) === -1) {
        this.types.push(type);
    }
    // TODO: 추가된 type을 AVal의 prop에 반영 전략 필요
    // 바로 전달할 것인가?
};

function mkInternalProp(prop) {
    if (prop === '✖')
        console.log("[Error]");
    return '*' + prop;
}
/**
 * @param {string} prop
 * @returns {AVal}
 */
AVal.prototype.getProp = function (prop) {
    if (prop === '✖') {
        // ✖ is the bogus property name added for error recovery.
        return AValNull;
    }
    var inProp = mkInternalProp(prop);
    if (inProp in this.props) {
        // Assume AVal has the merged AVal for the prop.
        return this.props[inProp];
    } else {
        return AValNull;
    }
};

// super class of all types
// I assume that each type is distinguishable by '===' operation.
function Type() {}
Type.prototype = Object.create(null);
var AllType = new Type;

// 1. object types
/**
 * @param {AVal} proto - AVal of prototype object
 * @param {string} name
 */
function ObjType(proto, name) {
    this.name = name;
    this.props = Object.create(null);
    // make __proto__ property
    this.joinAValToProp(proto, '__proto__');
}
ObjType.prototype = Object.create(AllType);
/**
 * @param {string} prop
 * @returns {AVal} AVal of the property
 */
ObjType.prototype.getProp = function (prop) {
    if (prop === '✖') {
        // ✖ is the bogus property name added during parsing error recovery.
        return AValNull;
    }
    var inProp = mkInternalProp(prop);
    if (inProp in this.props) {
        return this.props[inProp];
    } else {
        return AValNull;
    }
};
/**
 * @param {string} prop
 * @returns {boolean}
 */
ObjType.prototype.hasProp = function (prop) {
    if (prop === '✖')
        return AValNull;
    var inProp = mkInternalProp(prop);
    return (inProp in this.props);
};
/**
 * @param {Type} type
 * @param {string} prop
 */
ObjType.prototype.addTypeToProp = function (type, prop) {
    if (prop === '✖') return;
    var inProp = mkInternalProp(prop);
    if (!this.props[inProp]) {
        this.props[inProp] = new AVal;
    }
    if (this.props[inProp].hasType(type)) return;
    this.props[inProp].addType(type);
};
/**
 * @param {AVal} aval
 * @param {string} prop
 */
ObjType.prototype.joinAValToProp = function (aval, prop) {
    var self = this;
    aval.getTypes().forEach(function (type) {
        self.addTypeToProp(type, prop);
    });
};

var AllObjType = new ObjType(new AVal, '[AllObjType]');

// 2. primitive types
function PrimType(name) {
    this.name = name;
}
PrimType.prototype = Object.create(AllType);

// 3. function types
// the name is used for the type of the instances from the function
function FnType(name, argNames, sc) {
    // its __proto__ should be Function.prototype
    // for now it is AVal representing null - TODO use the correct proto
    ObjType.call(this, new AVal(PrimNumber), name);
    //ObjType.call(this, new AVal, name);
    // this.thisAVal = new AVal;
    this.paramNames = argNames;
    // this.retAVal = new AVal;
    this.sc = sc;
    // AVals for function usage
    this.usage = Object.create(null);
}
FnType.prototype = Object.create(AllObjType);

// returns AVals for given function usage
FnType.prototype.getUsageAVals = function (delta) {
    if (this.usage[delta] === undefined) {
        var params = [];
        // for 'this'
        params.push(new AVal);
        // for regular parameters
        for (var i = 1; i < this.paramNames.length; i++) {
            params.push(new AVal);
        }
        return this.usage[delta] = {params: params, ret: new AVal, exc: new AVal};
    } else return this.usage[delta];
};

// Make primitive types
var PrimNumber = new PrimType('number');
var PrimString = new PrimType('string');
var PrimBoolean = new PrimType('boolean');

// AbsNull represents all empty abstract values.
var AValNull = new AVal();
// You should not add any properties to it.
AValNull.props = null;

// export
exports.Type = Type;
exports.ObjType = ObjType;
exports.FnType = FnType;
exports.PrimNumber = PrimNumber;
exports.PrimString = PrimString;
exports.PrimBoolean = PrimBoolean;

exports.AVal = AVal;
exports.AValNull = AValNull;<commit_msg>Add a function for global object creation<commit_after>// for DEBUG
var count = 0;
// the abstract values for concrete values
// which are the sets of types
// We maintain 'props' for performance.
function AVal(type) {
    if (type) this.types = [type];
    else this.types = [];
    // OPTIONAL
    // for abstract values,
    // a prop is the merged AVals each types.
    this.props = Object.create(null);
    // for DEBUG
    this._id = count++;
}

/**
 * @returns {[Type]}
 */
AVal.prototype.getTypes = function () {
    return this.types;
};

/**
 * @returns {boolean}
 */
AVal.prototype.hasType = function (type) {
    return this.types.indexOf(type) > -1;
};

/**
 * @param {Type} type
 */
AVal.prototype.addType = function (type) {
    if (this.types.indexOf(type) === -1) {
        this.types.push(type);
    }
    // TODO: 추가된 type을 AVal의 prop에 반영 전략 필요
    // 바로 전달할 것인가?
};

function mkInternalProp(prop) {
    if (prop === '✖')
        console.log("[Error]");
    return '*' + prop;
}
/**
 * @param {string} prop
 * @returns {AVal}
 */
AVal.prototype.getProp = function (prop) {
    if (prop === '✖') {
        // ✖ is the bogus property name added for error recovery.
        return AValNull;
    }
    var inProp = mkInternalProp(prop);
    if (inProp in this.props) {
        // Assume AVal has the merged AVal for the prop.
        return this.props[inProp];
    } else {
        return AValNull;
    }
};

// super class of all types
// I assume that each type is distinguishable by '===' operation.
function Type() {}
Type.prototype = Object.create(null);
var AllType = new Type;

// 1. object types
/**
 * @param {AVal} proto - AVal of prototype object
 * @param {string} name
 */
function ObjType(proto, name) {
    this.name = name;
    this.props = Object.create(null);
    // make __proto__ property
    this.joinAValToProp(proto, '__proto__');
}
ObjType.prototype = Object.create(AllType);
/**
 * @param {string} prop
 * @returns {AVal} AVal of the property
 */
ObjType.prototype.getProp = function (prop) {
    if (prop === '✖') {
        // ✖ is the bogus property name added during parsing error recovery.
        return AValNull;
    }
    var inProp = mkInternalProp(prop);
    if (inProp in this.props) {
        return this.props[inProp];
    } else {
        return AValNull;
    }
};
/**
 * @param {string} prop
 * @returns {boolean}
 */
ObjType.prototype.hasProp = function (prop) {
    if (prop === '✖')
        return AValNull;
    var inProp = mkInternalProp(prop);
    return (inProp in this.props);
};
/**
 * @param {Type} type
 * @param {string} prop
 */
ObjType.prototype.addTypeToProp = function (type, prop) {
    if (prop === '✖') return;
    var inProp = mkInternalProp(prop);
    if (!this.props[inProp]) {
        this.props[inProp] = new AVal;
    }
    if (this.props[inProp].hasType(type)) return;
    this.props[inProp].addType(type);
};
/**
 * @param {AVal} aval
 * @param {string} prop
 */
ObjType.prototype.joinAValToProp = function (aval, prop) {
    var self = this;
    aval.getTypes().forEach(function (type) {
        self.addTypeToProp(type, prop);
    });
};

var AllObjType = new ObjType(new AVal, '[AllObjType]');

// make an Obj from the global scope
function mkObjFromGlobalScope(gScope) {
    var gObj = new ObjType(AValNull, "*global scope*");
    gObj.props = gScope.varMap;
    return gObj;
}

// 2. primitive types
function PrimType(name) {
    this.name = name;
}
PrimType.prototype = Object.create(AllType);

// 3. function types
// the name is used for the type of the instances from the function
function FnType(name, argNames, sc) {
    // its __proto__ should be Function.prototype
    // for now it is AVal representing null - TODO use the correct proto
    ObjType.call(this, new AVal(PrimNumber), name);
    //ObjType.call(this, new AVal, name);
    // this.thisAVal = new AVal;
    this.paramNames = argNames;
    // this.retAVal = new AVal;
    this.sc = sc;
    // AVals for function usage
    this.usage = Object.create(null);
}
FnType.prototype = Object.create(AllObjType);

// returns AVals for given function usage
FnType.prototype.getUsageAVals = function (delta) {
    if (this.usage[delta] === undefined) {
        var params = [];
        // for 'this'
        params.push(new AVal);
        // for regular parameters
        for (var i = 1; i < this.paramNames.length; i++) {
            params.push(new AVal);
        }
        return this.usage[delta] = {params: params, ret: new AVal, exc: new AVal};
    } else return this.usage[delta];
};

// Make primitive types
var PrimNumber = new PrimType('number');
var PrimString = new PrimType('string');
var PrimBoolean = new PrimType('boolean');

// AbsNull represents all empty abstract values.
var AValNull = new AVal();
// You should not add any properties to it.
AValNull.props = null;
// Adding types are ignored.
AValNull.addType = function () {};

// export
exports.Type = Type;
exports.ObjType = ObjType;
exports.FnType = FnType;
exports.PrimNumber = PrimNumber;
exports.PrimString = PrimString;
exports.PrimBoolean = PrimBoolean;
exports.mkObjFromGlobalScope = mkObjFromGlobalScope;

exports.AVal = AVal;
exports.AValNull = AValNull;<|endoftext|>


<commit_before>
/**
 * The following features are still outstanding: animation as a
 * function, placement as a function, inside, support for more triggers than
 * just mouse enter/leave, html tooltips, and selector delegation.
 */
angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] )

/**
 * The $tooltip service creates tooltip- and popover-like directives as well as
 * houses global options for them.
 */
.provider( '$tooltip', function () {
  // The default options tooltip and popover.
  var defaultOptions = {
    placement: 'top',
    animation: true,
    persist: false,
    popupDelay: 0
  };

  // Default hide triggers for each show trigger
  var triggerMap = {
    'mouseenter': 'mouseleave',
    'click': 'click',
    'focus': 'blur'
  };

  // The options specified to the provider globally.
  var globalOptions = {};

  /**
   * `options({})` allows global configuration of all tooltips in the
   * application.
   *
   *   var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
   *     // place tooltips left instead of top by default
   *     $tooltipProvider.options( { placement: 'left' } );
   *   });
   */
	this.options = function( value ) {
		angular.extend( globalOptions, value );
	};

  /**
   * This allows you to extend the set of trigger mappings available. E.g.:
   *
   *   $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
   */
  this.setTriggers = function setTriggers ( triggers ) {
    angular.extend( triggerMap, triggers );
  };

  /**
   * This is a helper function for translating camel-case to snake-case.
   */
  function snake_case(name){
    var regexp = /[A-Z]/g;
    var separator = '-';
    return name.replace(regexp, function(letter, pos) {
      return (pos ? separator : '') + letter.toLowerCase();
    });
  }

  /**
   * Returns the actual instance of the $tooltip service.
   * TODO support multiple triggers
   */
  this.$get = [ '$window', '$compile', '$timeout', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $document, $position, $interpolate ) {
    return function $tooltip ( type, prefix, defaultTriggerShow ) {
      var options = angular.extend( {}, defaultOptions, globalOptions );

      /**
       * Returns an object of show and hide triggers.
       *
       * If a trigger is supplied,
       * it is used to show the tooltip; otherwise, it will use the `trigger`
       * option passed to the `$tooltipProvider.options` method; else it will
       * default to the trigger supplied to this directive factory.
       *
       * The hide trigger is based on the show trigger. If the `trigger` option
       * was passed to the `$tooltipProvider.options` method, it will use the
       * mapped trigger from `triggerMap` or the passed trigger if the map is
       * undefined; otherwise, it uses the `triggerMap` value of the show
       * trigger; else it will just use the show trigger.
       */
      function getTriggers ( trigger ) {
        var show = trigger || options.trigger || defaultTriggerShow;
        var hide = triggerMap[show] || show;
        return {
          show: show,
          hide: hide
        };
      }

      var directiveName = snake_case( type );

      var startSym = $interpolate.startSymbol();
      var endSym = $interpolate.endSymbol();
      var template =
        '
'+
        '
';

      return {
        restrict: 'EA',
        compile: function (tElem, tAttrs) {
          var tooltipLinker = $compile( template );

          return function link ( scope, element, attrs ) {
            var tooltip;
            var tooltipLinkedScope;
            var transitionTimeout;
            var popupTimeout;
            var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false;
            var triggers = getTriggers( undefined );
            var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']);
            var ttScope = scope.$new(true);

            var positionTooltip = function () {

              var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody, ttScope.placementFallback);
              ttPosition.top += 'px';
              ttPosition.left += 'px';

              // Now set the calculated positioning.
              tooltip.css( ttPosition );
            };

            // By default, the tooltip is not open.
            // TODO add ability to start tooltip opened
            ttScope.isOpen = false;

            function toggleTooltipBind () {
              if ( ! ttScope.isOpen ) {
                showTooltipBind();
              } else {
                hideTooltipBind();
              }
            }

            // Show the tooltip with delay if specified, otherwise show it immediately
            function showTooltipBind() {
              if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) {
                return;
              }

              prepareTooltip();

              if ( ttScope.popupDelay ) {
                // Do nothing if the tooltip was already scheduled to pop-up.
                // This happens if show is triggered multiple times before any hide is triggered.
                if (!popupTimeout) {
                  popupTimeout = $timeout( show, ttScope.popupDelay, false );
                  popupTimeout.then(function(reposition){reposition();});
                }
              } else {
                show()();
              }
            }

            function hideTooltipBind () {
              scope.$apply(function () {
                hide();
              });
            }

            // Show the tooltip popup element.
            function show() {

              popupTimeout = null;

              // If there is a pending remove transition, we must cancel it, lest the
              // tooltip be mysteriously removed.
              if ( transitionTimeout ) {
                $timeout.cancel( transitionTimeout );
                transitionTimeout = null;
              }

              // Don't show empty tooltips.
              if ( ! ttScope.content ) {
                return angular.noop;
              }

              createTooltip();

              // Set the initial positioning.
              tooltip.css({ top: 0, left: 0, display: 'block' });

              // Now we add it to the DOM because need some info about it. But it's not
              // visible yet anyway.
              if ( appendToBody ) {
                  $document.find( 'body' ).append( tooltip );
              } else {
                element.after( tooltip );
              }

              positionTooltip();

              // And show the tooltip.
              ttScope.isOpen = true;
              ttScope.$digest(); // digest required as $apply is not called

              // Return positioning function as promise callback for correct
              // positioning after draw.
              return positionTooltip;
            }

            // Hide the tooltip popup element.
            function hide() {
              // First things first: we don't show it anymore.
              ttScope.isOpen = false;

              //if tooltip is going to be shown after delay, we must cancel this
              $timeout.cancel( popupTimeout );
              popupTimeout = null;

              // And now we remove it from the DOM. However, if we have animation, we
              // need to wait for it to expire beforehand.
              // FIXME: this is a placeholder for a port of the transitions library.
              if ( ttScope.animation ) {
                if (!transitionTimeout) {
                  transitionTimeout = $timeout(removeTooltip, 500);
                }
              } else {
                removeTooltip();
              }
            }

            function createTooltip() {
              var linkScope = scope;
              // There can only be one tooltip element per directive shown at once.
              if (tooltip) {
                removeTooltip();
              }
              tooltipLinkedScope = ttScope.$new();
              tooltip = tooltipLinker(tooltipLinkedScope, angular.noop);
            }

            function removeTooltip() {
              transitionTimeout = null;
              if (tooltip) {
                tooltip.remove();
                tooltip = null;
              }
              if (tooltipLinkedScope) {
                tooltipLinkedScope.$destroy();
                tooltipLinkedScope = null;
              }
            }

            function prepareTooltip() {
              prepPlacement();
              prepPlacementFallback();
              prepPopupDelay();
            }

            /**
             * Observe the relevant attributes.
             */
            attrs.$observe( type, function ( val ) {
              ttScope.content = val;

              if (!val && ttScope.isOpen ) {
                hide();
              }
            });

            attrs.$observe( prefix+'Title', function ( val ) {
              ttScope.title = val;
            });

            function prepPlacement() {
              var val = attrs[ prefix + 'Placement' ];
              ttScope.placement = angular.isDefined( val ) ? val : options.placement;
            }

            function prepPlacementFallback() {
              var val = attrs[ prefix + 'PlacementFallback' ];
              ttScope.placementFallback = angular.isDefined( val ) ? val : options.placementFallback;
            }

            function prepPopupDelay() {
              var val = attrs[ prefix + 'PopupDelay' ];
              var delay = parseInt( val, 10 );
              ttScope.popupDelay = ! isNaN(delay) ? delay : options.popupDelay;
            }

            var unregisterTriggers = function () {
              element.unbind(triggers.show, showTooltipBind);
              element.unbind(triggers.hide, hideTooltipBind);
            };

            function prepTriggers() {
              var val = attrs[ prefix + 'Trigger' ];
              unregisterTriggers();

              triggers = getTriggers( val );

              if ( triggers.show === triggers.hide ) {
                element.bind( triggers.show, toggleTooltipBind );
              } else {
                element.bind( triggers.show, showTooltipBind );
                element.bind( triggers.hide, hideTooltipBind );
              }
            }
            prepTriggers();

            var animation = scope.$eval(attrs[prefix + 'Animation']);
            ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;

            var persist = scope.$eval(attrs[prefix + 'Persist']);
            ttScope.persist = angular.isDefined(persist) ? !!persist : options.persist;

            var appendToBodyVal = scope.$eval(attrs[prefix + 'AppendToBody']);
            appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;

            // if a tooltip is attached to  we need to remove it on
            // location change as its parent scope will probably not be destroyed
            // by the change.
            if ( appendToBody ) {
              scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () {
              if ( ttScope.isOpen && !ttScope.persist ) {
                hide();
              }
            });
            }

            // Make sure tooltip is destroyed and removed.
            scope.$on('$destroy', function onDestroyTooltip() {
              $timeout.cancel( transitionTimeout );
              $timeout.cancel( popupTimeout );
              unregisterTriggers();
              removeTooltip();
              ttScope = null;
            });
          };
        }
      };
    };
  }];
})

.directive( 'tooltipPopup', function () {
  return {
    restrict: 'EA',
    replace: true,
    scope: { content: '@', placement: '@', animation: '&', isOpen: '&', persist: '&' },
    templateUrl: 'template/tooltip/tooltip-popup.html'
  };
})

.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) {
  return $tooltip( 'tooltip', 'tooltip', 'mouseenter' );
}])

.directive( 'tooltipHtmlUnsafePopup', function () {
  return {
    restrict: 'EA',
    replace: true,
    scope: { content: '@', placement: '@', animation: '&', isOpen: '&', persist: '&' },
    templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html'
  };
})

.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) {
  return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' );
}]);
<commit_msg>fix(tooltip): Fix logic on empty tooltips.<commit_after>
/**
 * The following features are still outstanding: animation as a
 * function, placement as a function, inside, support for more triggers than
 * just mouse enter/leave, html tooltips, and selector delegation.
 */
angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] )

/**
 * The $tooltip service creates tooltip- and popover-like directives as well as
 * houses global options for them.
 */
.provider( '$tooltip', function () {
  // The default options tooltip and popover.
  var defaultOptions = {
    placement: 'top',
    animation: true,
    persist: false,
    popupDelay: 0
  };

  // Default hide triggers for each show trigger
  var triggerMap = {
    'mouseenter': 'mouseleave',
    'click': 'click',
    'focus': 'blur'
  };

  // The options specified to the provider globally.
  var globalOptions = {};

  /**
   * `options({})` allows global configuration of all tooltips in the
   * application.
   *
   *   var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
   *     // place tooltips left instead of top by default
   *     $tooltipProvider.options( { placement: 'left' } );
   *   });
   */
	this.options = function( value ) {
		angular.extend( globalOptions, value );
	};

  /**
   * This allows you to extend the set of trigger mappings available. E.g.:
   *
   *   $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
   */
  this.setTriggers = function setTriggers ( triggers ) {
    angular.extend( triggerMap, triggers );
  };

  /**
   * This is a helper function for translating camel-case to snake-case.
   */
  function snake_case(name){
    var regexp = /[A-Z]/g;
    var separator = '-';
    return name.replace(regexp, function(letter, pos) {
      return (pos ? separator : '') + letter.toLowerCase();
    });
  }

  /**
   * Returns the actual instance of the $tooltip service.
   * TODO support multiple triggers
   */
  this.$get = [ '$window', '$compile', '$timeout', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $document, $position, $interpolate ) {
    return function $tooltip ( type, prefix, defaultTriggerShow ) {
      var options = angular.extend( {}, defaultOptions, globalOptions );

      /**
       * Returns an object of show and hide triggers.
       *
       * If a trigger is supplied,
       * it is used to show the tooltip; otherwise, it will use the `trigger`
       * option passed to the `$tooltipProvider.options` method; else it will
       * default to the trigger supplied to this directive factory.
       *
       * The hide trigger is based on the show trigger. If the `trigger` option
       * was passed to the `$tooltipProvider.options` method, it will use the
       * mapped trigger from `triggerMap` or the passed trigger if the map is
       * undefined; otherwise, it uses the `triggerMap` value of the show
       * trigger; else it will just use the show trigger.
       */
      function getTriggers ( trigger ) {
        var show = trigger || options.trigger || defaultTriggerShow;
        var hide = triggerMap[show] || show;
        return {
          show: show,
          hide: hide
        };
      }

      var directiveName = snake_case( type );

      var startSym = $interpolate.startSymbol();
      var endSym = $interpolate.endSymbol();
      var template =
        '
'+
        '
';

      return {
        restrict: 'EA',
        compile: function (tElem, tAttrs) {
          var tooltipLinker = $compile( template );

          return function link ( scope, element, attrs ) {
            var tooltip;
            var tooltipLinkedScope;
            var transitionTimeout;
            var popupTimeout;
            var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false;
            var triggers = getTriggers( undefined );
            var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']);
            var ttScope = scope.$new(true);

            var positionTooltip = function () {

              var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody, ttScope.placementFallback);
              ttPosition.top += 'px';
              ttPosition.left += 'px';

              // Now set the calculated positioning.
              tooltip.css( ttPosition );
            };

            // By default, the tooltip is not open.
            // TODO add ability to start tooltip opened
            ttScope.isOpen = false;

            function toggleTooltipBind () {
              if ( ! ttScope.isOpen ) {
                showTooltipBind();
              } else {
                hideTooltipBind();
              }
            }

            // Show the tooltip with delay if specified, otherwise show it immediately
            function showTooltipBind() {
              if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) {
                return;
              }

              prepareTooltip();

              if ( ttScope.popupDelay ) {
                // Do nothing if the tooltip was already scheduled to pop-up.
                // This happens if show is triggered multiple times before any hide is triggered.
                if (!popupTimeout) {
                  popupTimeout = $timeout( show, ttScope.popupDelay, false );
                  popupTimeout.then(function(reposition){reposition();});
                }
              } else {
                show()();
              }
            }

            function hideTooltipBind () {
              scope.$apply(function () {
                hide();
              });
            }

            // Show the tooltip popup element.
            function show() {

              popupTimeout = null;

              // If there is a pending remove transition, we must cancel it, lest the
              // tooltip be mysteriously removed.
              if ( transitionTimeout ) {
                $timeout.cancel( transitionTimeout );
                transitionTimeout = null;
              }

              // Don't show empty tooltips.
              if ( attrs[type] && !ttScope.content ) {
                return angular.noop;
              }

              createTooltip();

              // Set the initial positioning.
              tooltip.css({ top: 0, left: 0, display: 'block' });

              // Now we add it to the DOM because need some info about it. But it's not
              // visible yet anyway.
              if ( appendToBody ) {
                  $document.find( 'body' ).append( tooltip );
              } else {
                element.after( tooltip );
              }

              positionTooltip();

              // And show the tooltip.
              ttScope.isOpen = true;
              ttScope.$digest(); // digest required as $apply is not called

              // Return positioning function as promise callback for correct
              // positioning after draw.
              return positionTooltip;
            }

            // Hide the tooltip popup element.
            function hide() {
              // First things first: we don't show it anymore.
              ttScope.isOpen = false;

              //if tooltip is going to be shown after delay, we must cancel this
              $timeout.cancel( popupTimeout );
              popupTimeout = null;

              // And now we remove it from the DOM. However, if we have animation, we
              // need to wait for it to expire beforehand.
              // FIXME: this is a placeholder for a port of the transitions library.
              if ( ttScope.animation ) {
                if (!transitionTimeout) {
                  transitionTimeout = $timeout(removeTooltip, 500);
                }
              } else {
                removeTooltip();
              }
            }

            function createTooltip() {
              var linkScope = scope;
              // There can only be one tooltip element per directive shown at once.
              if (tooltip) {
                removeTooltip();
              }
              tooltipLinkedScope = ttScope.$new();
              tooltip = tooltipLinker(tooltipLinkedScope, angular.noop);
            }

            function removeTooltip() {
              transitionTimeout = null;
              if (tooltip) {
                tooltip.remove();
                tooltip = null;
              }
              if (tooltipLinkedScope) {
                tooltipLinkedScope.$destroy();
                tooltipLinkedScope = null;
              }
            }

            function prepareTooltip() {
              prepPlacement();
              prepPlacementFallback();
              prepPopupDelay();
            }

            /**
             * Observe the relevant attributes.
             */
            attrs.$observe( type, function ( val ) {
              ttScope.content = val;

              if (!val && ttScope.isOpen ) {
                hide();
              }
            });

            attrs.$observe( prefix+'Title', function ( val ) {
              ttScope.title = val;
            });

            function prepPlacement() {
              var val = attrs[ prefix + 'Placement' ];
              ttScope.placement = angular.isDefined( val ) ? val : options.placement;
            }

            function prepPlacementFallback() {
              var val = attrs[ prefix + 'PlacementFallback' ];
              ttScope.placementFallback = angular.isDefined( val ) ? val : options.placementFallback;
            }

            function prepPopupDelay() {
              var val = attrs[ prefix + 'PopupDelay' ];
              var delay = parseInt( val, 10 );
              ttScope.popupDelay = ! isNaN(delay) ? delay : options.popupDelay;
            }

            var unregisterTriggers = function () {
              element.unbind(triggers.show, showTooltipBind);
              element.unbind(triggers.hide, hideTooltipBind);
            };

            function prepTriggers() {
              var val = attrs[ prefix + 'Trigger' ];
              unregisterTriggers();

              triggers = getTriggers( val );

              if ( triggers.show === triggers.hide ) {
                element.bind( triggers.show, toggleTooltipBind );
              } else {
                element.bind( triggers.show, showTooltipBind );
                element.bind( triggers.hide, hideTooltipBind );
              }
            }
            prepTriggers();

            var animation = scope.$eval(attrs[prefix + 'Animation']);
            ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;

            var persist = scope.$eval(attrs[prefix + 'Persist']);
            ttScope.persist = angular.isDefined(persist) ? !!persist : options.persist;

            var appendToBodyVal = scope.$eval(attrs[prefix + 'AppendToBody']);
            appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;

            // if a tooltip is attached to  we need to remove it on
            // location change as its parent scope will probably not be destroyed
            // by the change.
            if ( appendToBody ) {
              scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () {
              if ( ttScope.isOpen && !ttScope.persist ) {
                hide();
              }
            });
            }

            // Make sure tooltip is destroyed and removed.
            scope.$on('$destroy', function onDestroyTooltip() {
              $timeout.cancel( transitionTimeout );
              $timeout.cancel( popupTimeout );
              unregisterTriggers();
              removeTooltip();
              ttScope = null;
            });
          };
        }
      };
    };
  }];
})

.directive( 'tooltipPopup', function () {
  return {
    restrict: 'EA',
    replace: true,
    scope: { content: '@', placement: '@', animation: '&', isOpen: '&', persist: '&' },
    templateUrl: 'template/tooltip/tooltip-popup.html'
  };
})

.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) {
  return $tooltip( 'tooltip', 'tooltip', 'mouseenter' );
}])

.directive( 'tooltipHtmlUnsafePopup', function () {
  return {
    restrict: 'EA',
    replace: true,
    scope: { content: '@', placement: '@', animation: '&', isOpen: '&', persist: '&' },
    templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html'
  };
})

.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) {
  return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' );
}]);
<|endoftext|>


<commit_before>'use strict';
var cp = require('child_process');
var child = cp.fork(__dirname + '/child.js');
var passport = require('passport');
var DropboxOAuth2Strategy = require('passport-dropbox-oauth2').Strategy;
var active = false;

module.exports = function (options) {
  return {
    initialize: function () {
      active = true;
      passport.use(new DropboxOAuth2Strategy({
        clientID: options.dropbox.id,
        clientSecret: options.dropbox.secret,
        callbackURL: 'http://jsbin.dev/auth/dropbox/callback'
      }, function (accessToken, refreshToken, profile, done) {
        done(null, profile);
      }
    ));
    }
  };
};

  saveBin: function(bin, user) {
    child.send({
      bin: bin,
      user: user
    });
  }

};
<commit_msg>Reimplement saveBin API<commit_after>'use strict';
var cp = require('child_process');
var child = cp.fork(__dirname + '/child.js');
var passport = require('passport');
var DropboxOAuth2Strategy = require('passport-dropbox-oauth2').Strategy;
var active = false;

module.exports = function (options) {
  return {
    initialize: function () {
      active = true;
      passport.use(new DropboxOAuth2Strategy({
        clientID: options.dropbox.id,
        clientSecret: options.dropbox.secret,
        callbackURL: 'http://jsbin.dev/auth/dropbox/callback'
      }, function (accessToken, refreshToken, profile, done) {
        done(null, profile);
      }
    ));
    }
  };
};

module.exports.saveBin = function (bin, user) {
  if (!active) {
    return;
  }
  child.send({
    bin: bin,
    user: user
  });
};
<|endoftext|>