<commit_before>'use strict';

var net = require('net')
  , EventEmitter = require('events').EventEmitter
  , util = require('util')
  , ltx = require('ltx')
  , reconnect = require('reconnect-core')
  , StreamParser = require('./stream_parser')
  , starttls = require('tls-connect')
  , debug = require('debug')('connection')

var NS_XMPP_TLS = 'urn:ietf:params:xml:ns:xmpp-tls'
var NS_STREAM = 'http://etherx.jabber.org/streams'
var NS_XMPP_STREAMS = 'urn:ietf:params:xml:ns:xmpp-streams'

var INITIAL_RECONNECT_DELAY =  1e3
var MAX_RECONNECT_DELAY     = 30e3
function defaultInjection(emitter, opts) {
    return { /* jshint -W014 */
        initialDelay: (opts && (opts.initialReconnectDelay
                            ||  opts.reconnectDelay)) || INITIAL_RECONNECT_DELAY,
        maxDelay: (opts &&   opts.maxReconnectDelay)  || MAX_RECONNECT_DELAY,
        immediate: opts && opts.socket && typeof opts.socket !== 'function',
        type:      opts && opts.delayType,
        emitter:   emitter
    }
}

/**
 Base class for connection-based streams (TCP).
 The socket parameter is optional for incoming connections.
*/
function Connection(opts) {

    EventEmitter.call(this)

    this.streamAttrs = (opts && opts.streamAttrs) || {}
    this.xmlns = (opts && opts.xmlns) || {}
    this.xmlns.stream = NS_STREAM

    this.rejectUnauthorized = (opts && opts.rejectUnauthorized) ? true : false
    this.requestCert = (opts && opts.requestCert) ? true : false
    this.servername = (opts && opts.servername)

    this._setupSocket(defaultInjection(this, opts))
    this.once('reconnect', function () {
        this.reconnect = opts && opts.reconnect
    })
}

util.inherits(Connection, EventEmitter)

Connection.prototype.NS_XMPP_TLS = NS_XMPP_TLS
Connection.NS_STREAM = NS_STREAM
Connection.prototype.NS_XMPP_STREAMS = NS_XMPP_STREAMS
// Defaults
Connection.prototype.allowTLS = true

Connection.prototype._setupSocket = function (params) {
    var previousOptions = {}
    var inject = reconnect(function (opts) {
        var previousSocket = this.socket
        /* if this opts.preserve is on
         * the previous options are stored until next time.
         * this is needed to restore from a setSecure call.
         */
        if (opts.preserve === 'on') {
            opts.preserve = previousOptions
            previousOptions = opts
        } else if (opts.preserve) {
            // switch back to the preversed options
            opts = previousOptions = opts.preserve
        } else {
            // keep some state for eg SRV.connect
            opts = previousOptions = opts || previousOptions
        }

        if (typeof opts.socket === 'function') {
            /* lazy evaluation
             * (can be retriggered by calling connection.connect()
             *  without arguments after a previous
             *  connection.connect({socket:function() { … }})) */
            this.socket = opts.socket.call(this)
        } else {
            // only use this socket once
            this.socket = opts.socket
            opts.socket = null
            this.once('connect', function () {
                inject.param.immediate = false
            })
        }
        this.socket = this.socket || new net.Socket()
        if (previousSocket !== this.socket)
            this.setupStream()
        return this.socket
    }.bind(this))

    inject(inject.param = params)

    this.on('connection', function () {
        if (!this.parser)
            this.startParser()
    })
}

/**
 Used by both the constructor and by reinitialization in setSecure().
*/
Connection.prototype.setupStream = function() {
    this.socket.on('end', this.onEnd.bind(this))
    this.socket.on('data', this.onData.bind(this))
    this.socket.on('close', this.onClose.bind(this))
    // let them sniff unparsed XML
    this.socket.on('data',  this.emit.bind(this, 'data'))
    this.socket.on('drain', this.emit.bind(this, 'drain'))
    // ignore errors after disconnect
    this.socket.on('error', function () { })

    /**
     * This is optimized for continuous TCP streams. If your "socket"
     * actually transports frames (WebSockets) and you can't have
     * stanzas split across those, use:
     *     cb(el.toString())
     */
    if (!this.socket.serializeStanza) {
        this.socket.serializeStanza = function(el, cb) {
            // Continuously write out
            el.write(function(s) {
                cb(s)
            })
        }
    }
}

Connection.prototype.pause = function() {
    if (this.socket.pause) this.socket.pause()
}

Connection.prototype.resume = function() {
    if (this.socket.resume) this.socket.resume()
}

/** Climbs the stanza up if a child was passed,
    but you can send strings and buffers too.

    Returns whether the socket flushed data.
*/
Connection.prototype.send = function(stanza) {
    var flushed = true
    if (!this.socket) {
        return // Doh!
    }
    if (!this.socket.writable) {
        this.socket.end()
        return
    }

    debug('send: ' + stanza.toString())
    if (stanza.root) {
        var el = this.rmXmlns(stanza.root())
        this.socket.serializeStanza(el, function(s) {
            flushed = this.write(s)
        }.bind(this.socket))
    } else {
        flushed = this.socket.write(stanza)
    }
    return flushed
}

Connection.prototype.startParser = function() {
    var self = this
    this.parser = new StreamParser.StreamParser(this.maxStanzaSize)

    this.parser.on('streamStart', function(attrs) {
        /* We need those xmlns often, store them extra */
        self.streamNsAttrs = {}
        for (var k in attrs) {
            if (k === 'xmlns' || (k.substr(0, 6) === 'xmlns:'))
                self.streamNsAttrs[k] = attrs[k]
        }

        /* Notify in case we don't wait for <stream:features/>
           (Component or non-1.0 streams)
         */
        self.emit('streamStart', attrs)
    })
    this.parser.on('stanza', function(stanza) {
        self.onStanza(self.addStreamNs(stanza))
    })
    this.parser.on('error', function(e) {
        self.error(e.condition || 'internal-server-error', e.message)
    })
    this.parser.once('end', function() {
        self.stopParser()
        if (self.reconnect)
            self.once('reconnect', self.startParser.bind(self))
        else
            self.end()
    })
}

Connection.prototype.stopParser = function() {
    /* No more events, please (may happen however) */
    if (this.parser) {
        var parser = this.parser
        /* Get GC'ed */
        delete this.parser
        parser.end()
    }
}

Connection.prototype.startStream = function() {
    var attrs = {}
    for (var k in this.xmlns) {
        if (this.xmlns.hasOwnProperty(k)) {
            if (!k)
                attrs.xmlns = this.xmlns[k]
            else
                attrs['xmlns:' + k] = this.xmlns[k]
        }
    }
    for (k in this.streamAttrs) {
        if (this.streamAttrs.hasOwnProperty(k))
            attrs[k] = this.streamAttrs[k]
    }

    if (this.streamTo) { // in case of a component connecting
        attrs.to = this.streamTo
    }

    var el = new ltx.Element('stream:stream', attrs)
    // make it non-empty to cut the closing tag
    el.t(' ')
    var s = el.toString()
    this.send(s.substr(0, s.indexOf(' </stream:stream>')))

    this.streamOpened = true
}

Connection.prototype.onData = function(data) {
    debug('recieve: ' + data.toString('utf8'))
    if (this.parser)
        this.parser.write(data)
}

Connection.prototype.setSecure = function(credentials, isServer) {
    // Remove old event listeners
    this.socket.removeAllListeners('data')
    // retain socket 'end' listeners because ssl layer doesn't support it
    this.socket.removeAllListeners('drain')
    this.socket.removeAllListeners('close')
    // remove idle_timeout
    if (this.socket.clearTimer)
        this.socket.clearTimer()

    var cleartext = starttls({
        socket: this.socket,
        rejectUnauthorized: this.rejectUnauthorized,
        credentials: credentials || this.credentials,
        requestCert: this.requestCert,
        isServer: !!isServer
    }, function() {
        this.isSecure = true
        this.once('disconnect', function () {
            this.isSecure = false
        })
        cleartext.emit('connect', cleartext)
    }.bind(this))
    cleartext.on('clientError', this.emit.bind(this, 'error'))
    if (!this.reconnect)
        this.once('reconnect', function () {this.reconnect = false})
    // if we reconnect we need to get back to the previous socket creation
    this.listen({socket:cleartext, preserve:'on'})
}

function getAllText(el) {
    return el.children.reduce(function (text, child) {
        return text + (child.children ? getAllText(child) : child)
    }, '')
}

/**
 * This is not an event listener, but takes care of the TLS handshake
 * before 'stanza' events are emitted to the derived classes.
 */
Connection.prototype.onStanza = function(stanza) {
    if (stanza.is('error', NS_STREAM)) {
        var error = new Error(getAllText(stanza) || '' + stanza)
        error.stanza = stanza
        this.socket.emit('error', error)
    } else if (stanza.is('features', this.NS_STREAM) &&
        this.allowTLS &&
        !this.isSecure &&
        stanza.getChild('starttls', this.NS_XMPP_TLS)) {
        /* Signal willingness to perform TLS handshake */
        this.send(new ltx.Element('starttls', { xmlns: this.NS_XMPP_TLS }))
    } else if (this.allowTLS &&
        stanza.is('proceed', this.NS_XMPP_TLS)) {
        /* Server is waiting for TLS handshake */
        this.setSecure()
    } else {
        this.emit('stanza', stanza)
    }
}

/**
 * Add stream xmlns to a stanza
 *
 * Does not add our default xmlns as it is different for
 * C2S/S2S/Component connections.
 */
Connection.prototype.addStreamNs = function(stanza) {
    for (var attr in this.streamNsAttrs) {
        if (!stanza.attrs[attr] &&
            !((attr === 'xmlns') && (this.streamNsAttrs[attr] === this.xmlns['']))
           ) {
            stanza.attrs[attr] = this.streamNsAttrs[attr]
        }
    }
    return stanza
}

/**
 * Remove superfluous xmlns that were aleady declared in
 * our <stream:stream>
 */
Connection.prototype.rmXmlns = function(stanza) {
    for (var prefix in this.xmlns) {
        var attr = prefix ? 'xmlns:' + prefix : 'xmlns'
        if (stanza.attrs[attr] === this.xmlns[prefix])
            delete stanza.attrs[attr]
    }
    return stanza
}

/**
 * XMPP-style end connection for user
 */
Connection.prototype.onEnd = function() {
    if (this.socket && this.socket.writable) {
        if (this.streamOpened) {
            this.socket.write('</stream:stream>')
            delete this.streamOpened
        }
    }
    if (!this.reconnect)
        this.emit('end')
}

Connection.prototype.onClose = function() {
    if (!this.reconnect)
        this.emit('close')
}

/**
 * End connection with stream error.
 * Emits 'error' event too.
 *
 * @param {String} condition XMPP error condition, see RFC3920 4.7.3. Defined Conditions
 * @param {String} text Optional error message
 */
Connection.prototype.error = function(condition, message) {
    this.emit('error', new Error(message))

    if (!this.socket || !this.socket.writable) return

    /* RFC 3920, 4.7.1 stream-level errors rules */
    if (!this.streamOpened) this.startStream()

    var e = new ltx.Element('stream:error')
    e.c(condition, { xmlns: NS_XMPP_STREAMS })
    if (message) {
        e.c(
            'text',
            { xmlns: NS_XMPP_STREAMS, 'xml:lang': 'en' }
        ).t(message)
    }

    this.send(e)
    this.end()
}

module.exports = Connection
<commit_msg>restart parser when upgrading to tls<commit_after>'use strict';

var net = require('net')
  , EventEmitter = require('events').EventEmitter
  , util = require('util')
  , ltx = require('ltx')
  , reconnect = require('reconnect-core')
  , StreamParser = require('./stream_parser')
  , starttls = require('tls-connect')
  , debug = require('debug')('connection')

var NS_XMPP_TLS = 'urn:ietf:params:xml:ns:xmpp-tls'
var NS_STREAM = 'http://etherx.jabber.org/streams'
var NS_XMPP_STREAMS = 'urn:ietf:params:xml:ns:xmpp-streams'

var INITIAL_RECONNECT_DELAY =  1e3
var MAX_RECONNECT_DELAY     = 30e3
function defaultInjection(emitter, opts) {
    return { /* jshint -W014 */
        initialDelay: (opts && (opts.initialReconnectDelay
                            ||  opts.reconnectDelay)) || INITIAL_RECONNECT_DELAY,
        maxDelay: (opts &&   opts.maxReconnectDelay)  || MAX_RECONNECT_DELAY,
        immediate: opts && opts.socket && typeof opts.socket !== 'function',
        type:      opts && opts.delayType,
        emitter:   emitter
    }
}

/**
 Base class for connection-based streams (TCP).
 The socket parameter is optional for incoming connections.
*/
function Connection(opts) {

    EventEmitter.call(this)

    this.streamAttrs = (opts && opts.streamAttrs) || {}
    this.xmlns = (opts && opts.xmlns) || {}
    this.xmlns.stream = NS_STREAM

    this.rejectUnauthorized = (opts && opts.rejectUnauthorized) ? true : false
    this.requestCert = (opts && opts.requestCert) ? true : false
    this.servername = (opts && opts.servername)

    this._setupSocket(defaultInjection(this, opts))
    this.once('reconnect', function () {
        this.reconnect = opts && opts.reconnect
    })
}

util.inherits(Connection, EventEmitter)

Connection.prototype.NS_XMPP_TLS = NS_XMPP_TLS
Connection.NS_STREAM = NS_STREAM
Connection.prototype.NS_XMPP_STREAMS = NS_XMPP_STREAMS
// Defaults
Connection.prototype.allowTLS = true

Connection.prototype._setupSocket = function (params) {
    var previousOptions = {}
    var inject = reconnect(function (opts) {
        var previousSocket = this.socket
        /* if this opts.preserve is on
         * the previous options are stored until next time.
         * this is needed to restore from a setSecure call.
         */
        if (opts.preserve === 'on') {
            opts.preserve = previousOptions
            previousOptions = opts
        } else if (opts.preserve) {
            // switch back to the preversed options
            opts = previousOptions = opts.preserve
        } else {
            // keep some state for eg SRV.connect
            opts = previousOptions = opts || previousOptions
        }

        if (typeof opts.socket === 'function') {
            /* lazy evaluation
             * (can be retriggered by calling connection.connect()
             *  without arguments after a previous
             *  connection.connect({socket:function() { … }})) */
            this.socket = opts.socket.call(this)
        } else {
            // only use this socket once
            this.socket = opts.socket
            opts.socket = null
            this.once('connect', function () {
                inject.param.immediate = false
            })
        }
        this.socket = this.socket || new net.Socket()
        if (previousSocket !== this.socket)
            this.setupStream()
        return this.socket
    }.bind(this))

    inject(inject.param = params)

    this.on('connection', function () {
        if (!this.parser)
            this.startParser()
    })
}

/**
 Used by both the constructor and by reinitialization in setSecure().
*/
Connection.prototype.setupStream = function() {
    this.socket.on('end', this.onEnd.bind(this))
    this.socket.on('data', this.onData.bind(this))
    this.socket.on('close', this.onClose.bind(this))
    // let them sniff unparsed XML
    this.socket.on('data',  this.emit.bind(this, 'data'))
    this.socket.on('drain', this.emit.bind(this, 'drain'))
    // ignore errors after disconnect
    this.socket.on('error', function () { })

    /**
     * This is optimized for continuous TCP streams. If your "socket"
     * actually transports frames (WebSockets) and you can't have
     * stanzas split across those, use:
     *     cb(el.toString())
     */
    if (!this.socket.serializeStanza) {
        this.socket.serializeStanza = function(el, cb) {
            // Continuously write out
            el.write(function(s) {
                cb(s)
            })
        }
    }
}

Connection.prototype.pause = function() {
    if (this.socket.pause) this.socket.pause()
}

Connection.prototype.resume = function() {
    if (this.socket.resume) this.socket.resume()
}

/** Climbs the stanza up if a child was passed,
    but you can send strings and buffers too.

    Returns whether the socket flushed data.
*/
Connection.prototype.send = function(stanza) {
    var flushed = true
    if (!this.socket) {
        return // Doh!
    }
    if (!this.socket.writable) {
        this.socket.end()
        return
    }

    debug('send: ' + stanza.toString())
    if (stanza.root) {
        var el = this.rmXmlns(stanza.root())
        this.socket.serializeStanza(el, function(s) {
            flushed = this.write(s)
        }.bind(this.socket))
    } else {
        flushed = this.socket.write(stanza)
    }
    return flushed
}

Connection.prototype.startParser = function() {
    var self = this
    this.parser = new StreamParser.StreamParser(this.maxStanzaSize)

    this.parser.on('streamStart', function(attrs) {
        /* We need those xmlns often, store them extra */
        self.streamNsAttrs = {}
        for (var k in attrs) {
            if (k === 'xmlns' || (k.substr(0, 6) === 'xmlns:'))
                self.streamNsAttrs[k] = attrs[k]
        }

        /* Notify in case we don't wait for <stream:features/>
           (Component or non-1.0 streams)
         */
        self.emit('streamStart', attrs)
    })
    this.parser.on('stanza', function(stanza) {
        self.onStanza(self.addStreamNs(stanza))
    })
    this.parser.on('error', function(e) {
        self.error(e.condition || 'internal-server-error', e.message)
    })
    this.parser.once('end', function() {
        self.stopParser()
        if (self.reconnect)
            self.once('reconnect', self.startParser.bind(self))
        else
            self.end()
    })
}

Connection.prototype.stopParser = function() {
    /* No more events, please (may happen however) */
    if (this.parser) {
        var parser = this.parser
        /* Get GC'ed */
        delete this.parser
        parser.end()
    }
}

Connection.prototype.startStream = function() {
    var attrs = {}
    for (var k in this.xmlns) {
        if (this.xmlns.hasOwnProperty(k)) {
            if (!k)
                attrs.xmlns = this.xmlns[k]
            else
                attrs['xmlns:' + k] = this.xmlns[k]
        }
    }
    for (k in this.streamAttrs) {
        if (this.streamAttrs.hasOwnProperty(k))
            attrs[k] = this.streamAttrs[k]
    }

    if (this.streamTo) { // in case of a component connecting
        attrs.to = this.streamTo
    }

    var el = new ltx.Element('stream:stream', attrs)
    // make it non-empty to cut the closing tag
    el.t(' ')
    var s = el.toString()
    this.send(s.substr(0, s.indexOf(' </stream:stream>')))

    this.streamOpened = true
}

Connection.prototype.onData = function(data) {
    debug('recieve: ' + data.toString('utf8'))
    if (this.parser)
        this.parser.write(data)
}

Connection.prototype.setSecure = function(credentials, isServer) {
    // Remove old event listeners
    this.socket.removeAllListeners('data')
    // retain socket 'end' listeners because ssl layer doesn't support it
    this.socket.removeAllListeners('drain')
    this.socket.removeAllListeners('close')
    // remove idle_timeout
    if (this.socket.clearTimer)
        this.socket.clearTimer()

    var cleartext = starttls({
        socket: this.socket,
        rejectUnauthorized: this.rejectUnauthorized,
        credentials: credentials || this.credentials,
        requestCert: this.requestCert,
        isServer: !!isServer
    }, function() {
        this.isSecure = true
        this.once('disconnect', function () {
            this.isSecure = false
        })
        cleartext.emit('connect', cleartext)
    }.bind(this))
    cleartext.on('clientError', this.emit.bind(this, 'error'))
    if (!this.reconnect)
        this.once('reconnect', function () {this.reconnect = false})
    }
    this.stopParser()
    // if we reconnect we need to get back to the previous socket creation
    this.listen({socket:cleartext, preserve:'on'})
}

function getAllText(el) {
    return el.children.reduce(function (text, child) {
        return text + (child.children ? getAllText(child) : child)
    }, '')
}

/**
 * This is not an event listener, but takes care of the TLS handshake
 * before 'stanza' events are emitted to the derived classes.
 */
Connection.prototype.onStanza = function(stanza) {
    if (stanza.is('error', NS_STREAM)) {
        var error = new Error(getAllText(stanza) || '' + stanza)
        error.stanza = stanza
        this.socket.emit('error', error)
    } else if (stanza.is('features', this.NS_STREAM) &&
        this.allowTLS &&
        !this.isSecure &&
        stanza.getChild('starttls', this.NS_XMPP_TLS)) {
        /* Signal willingness to perform TLS handshake */
        this.send(new ltx.Element('starttls', { xmlns: this.NS_XMPP_TLS }))
    } else if (this.allowTLS &&
        stanza.is('proceed', this.NS_XMPP_TLS)) {
        /* Server is waiting for TLS handshake */
        this.setSecure()
    } else {
        this.emit('stanza', stanza)
    }
}

/**
 * Add stream xmlns to a stanza
 *
 * Does not add our default xmlns as it is different for
 * C2S/S2S/Component connections.
 */
Connection.prototype.addStreamNs = function(stanza) {
    for (var attr in this.streamNsAttrs) {
        if (!stanza.attrs[attr] &&
            !((attr === 'xmlns') && (this.streamNsAttrs[attr] === this.xmlns['']))
           ) {
            stanza.attrs[attr] = this.streamNsAttrs[attr]
        }
    }
    return stanza
}

/**
 * Remove superfluous xmlns that were aleady declared in
 * our <stream:stream>
 */
Connection.prototype.rmXmlns = function(stanza) {
    for (var prefix in this.xmlns) {
        var attr = prefix ? 'xmlns:' + prefix : 'xmlns'
        if (stanza.attrs[attr] === this.xmlns[prefix])
            delete stanza.attrs[attr]
    }
    return stanza
}

/**
 * XMPP-style end connection for user
 */
Connection.prototype.onEnd = function() {
    if (this.socket && this.socket.writable) {
        if (this.streamOpened) {
            this.socket.write('</stream:stream>')
            delete this.streamOpened
        }
    }
    if (!this.reconnect)
        this.emit('end')
}

Connection.prototype.onClose = function() {
    if (!this.reconnect)
        this.emit('close')
}

/**
 * End connection with stream error.
 * Emits 'error' event too.
 *
 * @param {String} condition XMPP error condition, see RFC3920 4.7.3. Defined Conditions
 * @param {String} text Optional error message
 */
Connection.prototype.error = function(condition, message) {
    this.emit('error', new Error(message))

    if (!this.socket || !this.socket.writable) return

    /* RFC 3920, 4.7.1 stream-level errors rules */
    if (!this.streamOpened) this.startStream()

    var e = new ltx.Element('stream:error')
    e.c(condition, { xmlns: NS_XMPP_STREAMS })
    if (message) {
        e.c(
            'text',
            { xmlns: NS_XMPP_STREAMS, 'xml:lang': 'en' }
        ).t(message)
    }

    this.send(e)
    this.end()
}

module.exports = Connection
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');

module.exports = function(RED) {
  var voices = [];
  speechSynthesis.onvoiceschanged = function() {
    voices = speechSynthesis.getVoices();
  };

  RED.nodes.registerType('espeak',{
    category: 'output',
    defaults: {
      name: {value: ''},
      variant: {value: ''},
      active: {value: true}
    },
    label: function() {
      return this.name || this.variant || 'espeak';
    },
    labelStyle: function() {
      return this.name ? 'node_label_italic' : '';
    },
    color:'#ffb6c1',
    inputs:1,
    outputs:0,
    icon: 'debug.png',
    align: 'right',
    button: {
      toggle: 'active',
      onclick: function() {

        var label = this.name||'espeak';
        var node = this;
        RED.comms.rpc('espeak', [this.id, (this.active ? 'enable' : 'disable')], function(result) {
          if (result == 200) {
            RED.notify(node._('debug.notification.activated', {label: label}), 'success');
          } else if (result == 201) {
            RED.notify(node._('debug.notification.deactivated', {label: label}), 'success');
          }
        });
      }
    },
    oneditprepare: function() {
      var selectedVariant = this.variant;
      var dropdown = document.getElementById('node-input-variant');

      voices.forEach(function(voice){
        var newVoice = document.createElement('OPTION');
        newVoice.text = voice.name;
        newVoice.value = voice.voiceURI;

        if(voice.voiceURI === selectedVariant){
          newVoice.selected = true;
        };

        dropdown.options.add(newVoice);
      });
    },

    onpaletteadd: function() {

      this.handleDebugMessage = function(t,o) {

        if(typeof o.msg === 'string'){
          try{
            o.msg = JSON.parse(o.msg);
          }
          catch(err){
            console.log('error parsing notification', err);
          }
        }

        var msg = o.msg;
        //do tts

        console.log('espeak', msg);
        var phrase = new SpeechSynthesisUtterance(String(msg.payload));
        var voice = _.find(voices, { voiceURI: o.variant });

        phrase.voice = voice;
        phrase.pitch = parseInt(msg.pitch, 10) || 1;
        phrase.speed = parseInt(msg.spped, 10) || 1;

        speechSynthesis.speak(phrase);
      };
      RED.comms.subscribe('espeak',this.handleDebugMessage);

    },
    onpaletteremove: function() {
      RED.comms.unsubscribe('espeak',this.handleDebugMessage);
      // RED.sidebar.removeTab("debug");
      // delete RED._debug;
    }
  });
};
<commit_msg>feature detection and error notifications for browsers without a voice<commit_after>'use strict';

var _ = require('lodash');

module.exports = function(RED) {
  var errorMessage = 'Your browser does not support web speech. Please use Google Chrome for this feature.'
  var voices = [];
  if(speechSynthesis){
    speechSynthesis.onvoiceschanged = function() {
      voices = speechSynthesis.getVoices();
    };
  } else {
    RED.notify(errorMessage, 'error');
  }

  RED.nodes.registerType('espeak',{
    category: 'output',
    defaults: {
      name: {value: ''},
      variant: {value: ''},
      active: {value: true}
    },
    label: function() {
      return this.name || this.variant || 'espeak';
    },
    labelStyle: function() {
      return this.name ? 'node_label_italic' : '';
    },
    color:'#ffb6c1',
    inputs:1,
    outputs:0,
    icon: 'debug.png',
    align: 'right',
    button: {
      toggle: 'active',
      onclick: function() {

        var label = this.name||'espeak';
        var node = this;
        RED.comms.rpc('espeak', [this.id, (this.active ? 'enable' : 'disable')], function(result) {
          if (result == 200) {
            RED.notify(node._('debug.notification.activated', {label: label}), 'success');
          } else if (result == 201) {
            RED.notify(node._('debug.notification.deactivated', {label: label}), 'success');
          }
        });
      }
    },
    oneditprepare: function() {
      var selectedVariant = this.variant;
      var dropdown = document.getElementById('node-input-variant');

      voices.forEach(function(voice){
        var newVoice = document.createElement('OPTION');
        newVoice.text = voice.name;
        newVoice.value = voice.voiceURI;

        if(voice.voiceURI === selectedVariant){
          newVoice.selected = true;
        };

        dropdown.options.add(newVoice);
      });
    },
    onpaletteadd: function() {

      this.handleEspeakMessage = function(t,o) {

        if(typeof o.msg === 'string'){
          try{
            o.msg = JSON.parse(o.msg);
          }
          catch(err){
            console.log('error parsing notification', err);
          }
        }

        var msg = o.msg;
        //do tts

        console.log('espeak', msg);
        var voice = _.find(voices, { voiceURI: o.variant });

        if(SpeechSynthesisUtterance && speechSynthesis && voice){
          console.log('everything is fine.. I promise.....', voice);
          var phrase = new SpeechSynthesisUtterance(String(msg.payload));

          phrase.voice = voice;
          phrase.pitch = parseInt(msg.pitch, 10) || 1;
          phrase.speed = parseInt(msg.spped, 10) || 1;

          speechSynthesis.speak(phrase);
        } else {
          RED.notify(errorMessage, 'error');
        }
      };
      RED.comms.subscribe('espeak',this.handleEspeakMessage);

    },
    onpaletteremove: function() {
      RED.comms.unsubscribe('espeak',this.handleDebugMessage);
      // RED.sidebar.removeTab("debug");
      // delete RED._debug;
    }
  });
};
<|endoftext|>


<commit_before>
var ruby = require('./ruby.js'),
    javascript = require('./javascript.js'),
    csharp = require('./csharp.js'),
    python = require('./python.js'),
    checker = require('./checker.js'),
    tokenizer = require('./tokenizer.js');

var fns = { };

for (n in ruby)
    fns['ruby_' + n] = ruby[n];
for (n in ruby)
    fns['python_' + n] = python[n];
for (n in javascript)
    fns['javascript_' + n] = javascript[n];
for (n in csharp)
    fns['csharp_' + n] = csharp[n];

function analyze(text) {
    var tokens = tokenizer.getTokens(text);
    var check = checker.check(text, tokens, fns);

    var max = 0;
    var result = null;

    for (n in check) {
        if (check[n] > max) {
            max = check[n];
            result = n;
        }
    }

    return result;
}

module.exports = {
    analyze: analyze
}<commit_msg>Refactor plangre.js for an easy browserfy<commit_after>
if (typeof ruby === 'undefined')
    var ruby = require('./ruby.js');

if (typeof javascript === 'undefined')
    var javascript = require('./javascript.js');

if (typeof csharp === 'undefined')
    var csharp = require('./csharp.js');

if (typeof python === 'undefined')
    var python = require('./python.js');

if (typeof checker === 'undefined')
    var checker = require('./checker.js');

if (typeof tokenizer === 'undefined')
    var tokenizer = require('./tokenizer.js');

var plangre = (function() {
    var fns = { };

    for (n in ruby)
        fns['ruby_' + n] = ruby[n];
    for (n in ruby)
        fns['python_' + n] = python[n];
    for (n in javascript)
        fns['javascript_' + n] = javascript[n];
    for (n in csharp)
        fns['csharp_' + n] = csharp[n];

    function analyze(text) {
        var tokens = tokenizer.getTokens(text);
        var check = checker.check(text, tokens, fns);

        var max = 0;
        var result = null;

        for (n in check) {
            if (check[n] > max) {
                max = check[n];
                result = n;
            }
        }

        return result;
    }

    return {
        analyze: analyze
    };
})();

if (module && module.exports)
    module.exports = plangre;
<|endoftext|>


<commit_before>import { entries } from "./utils"

/**
 * Algorythm's principle:
 * - When a constraint switches to the "invalid" or "uncertain" state, its children get the state "uncertain"
 * - When a constraint is evaluated and its value has changed, its children get the state "invalid"
 * - When an "invalid" constraint is retrieved, it is (re)-evaluated
 * - When an "uncertain" constraint is requested, its "invalid" and "uncertain" parents are udpated
 *   - as soon as one "uncertain" or "invalid" parent changed after its evaluation the constraint is
 *       re-evaluated. Because the dependencies are called in order of used, stopping as soon as one changed
 *       dependency is found ensure that we do not trigger the evaluation of a dependency that may
 *       not be used anymore.
 *   - if no parent changed (or all are already valid), the constraint switches to the "valid" state
 *       without being re-evaluated
 */

// Constraint's states:

/*
 * Means the constraint must be re-evaluated, at least one of the constraints it currently
 * depends own is known to have changed.
 */
const invalid = Symbol("invalid");

/*
 * The constraint does not need to be re-evaluated.
 */
const valid = Symbol("valid");

/*
 * It is not sure if the constraint must be re-evaluated. One of its ancestor
 * has been invalidated but it is not sure yet that one of its parents has actually changed. A constraint
 * may still be in the "uncertain" state when all of its parent has been re-evaluated without change
 * but must not be in this state if we know (for sure) that one of its parent has changed.
*/
const uncertain = Symbol("uncertain");

/*
 * The constraint is currently being evaluated. If its value is fetched again (i.e. in the
 * of cyclic dependencies), the cached value will be provided.
 * Because self-invalidating evaluation is forbidden it is not possible to invalidate or
 * set the constraint value when it is in this state (may change in the future).
 */
const evaluation = Symbol("evaluation");

/*
 * The constraint has been detached so that it can be garbaged collected.
 * Any link to its dependencies and the constraint that depends on its has been cut.
 * If fetched, it will now always return `undefined`.
 */
const detached = Symbol("detached");

/*
 * Golden rule: nothing gets out of an instance of this class without registering
 * itself as a dependency of the potential calling's constraint
 * (except if specified otherwise in arguments).
 */
export default class Constraint {

    /*
     * Create a constraint and set its value to *getter*.
     * See set.
     */
    constructor (getter) {
        this._getter = null;
        this._value = null;
        this._state = invalid;
        this._dependencies = new Set();
        this._dependentConstraints = new Set();
        // Registers handlers to be called when the constraint may have changed.
        // Contains an object (see onMayHaveChanged).
        this._mayHaveChangedListeners = [];
        this.set(getter);
    }

    /*
     * Set the constraint value.
     * If it is a function, this function will be used as a getter.
     * If it is another constraint, this constraint will be constrained
     * to this other constraint.
     */
    set(getter) {
        this._getter = Constraint._createGetter(getter);
        this.invalidate();
    }

    /*
     * Invalidate the constraint.
     * As a result, the next its value is fetched, it will be
     * re-evaluated.
     */
    invalidate(){
        if(this._state === evaluation){
            // self invalidating evaluation is forbidden for now.
            // TODO: use a real exception
            throw "Cannot invalidate a constraint still being evaluated."
        }

        this._setState(invalid);
        this._mayHaveChanged();
    }

    /*
     * Notify this constraint that it may have changed.
     * @param alreadyNotified the constraints that have already
     * been notified of this potential change and that should not
     * be notified again.
     */
    _mayHaveChanged(alreadyNotified=new Set()){
        // JS default arguments' evaluation is on method call
        // so default alreadyNotified will be a new set each
        // time _mayHaveChanged() is called without args.

        if(this._state === valid){
            this._setState(uncertain);
        }
        // Notify the on may have change listeners
        this._notifyListeners();
        // Specify that this constraint has been notified
        alreadyNotified.add(this);
        // Notify this constraint's dependent constraint
        // if they have not been yet notified.
        this._dependentConstraints.forEach((dc) => {
            if(!alreadyNotified.has(dc)){
                dc._mayHaveChanged(alreadyNotified);
            }
        });
    }

    /**
     * Get the constraint value, evaluating it if necessary
     * (i.e. if it is invalid).
     */
    get(regAsCallersDependency=true){
        if(this._state === detached) return;
        // If there is a caller, register this constraint as dependency
        // of this caller, and add the caller to the dependent
        // constraints.
        if(regAsCallersDependency){
            this._regAsCallersDependency();
        }

        if(this._state !== valid && this._state !== evaluation){
            this._update();
        }
        return this._value;
    }

    /*
     * Add the constraint as dependency of the constraint on top of the constraint's
     * stack (if any).
     */
    _regAsCallersDependency(){
        if(Constraint._constraintStack.length > 0){
            const caller = Constraint._constraintStack[Constraint._constraintStack.length - 1]
            caller._registerDependency(this);
        }
    }

    /**
     * Register a new constraint on which depends this constraint.
     */
    _registerDependency(dep){
        // This must (and should) not be called if the constraint has been detached.
        this._dependencies.add(dep);
        dep._dependentConstraints.add(this);
    }

    /**
     * Settle the constraint state between valid or invalid if its state is uncertain.
     * This does not update the constraint itself but is likely to update
     * some of its dependencies.
     */
    _settle(){
        if(this._state === uncertain){
            // Update the dependencies (in order) until it finds one that
            // changes.
            let someParentChanged = false;
            for(let p of this._dependencies){
                someParentChanged = p._update();
                if(someParentChanged) break;
            }
            if(!someParentChanged){
                // If no dependency has changed, the present constraint (this) is valid.
                this._setState(valid);
            }
            // If one dependency has changed it should have invalidated this
            // so this._state should be invalid now.
        }
        return this._state;
    }

    /**
     * Update the constraint's value.
     * @returns true if the constraint value has changed, false otherwise.
     */
    _update(){
        // If the state is uncertain, settle it between valid or invalid.
        this._settle();
        if(this._state === invalid){
            return this._evaluate();
        }
        return false;
    }

    _evaluate(){
        this._setState(evaluation);
        const oldValue = this._value;
        // Reset the dependencies (so that dependencies that are not used anymore are removed).
        const previousDependencies = this._dependencies;
        this._dependencies = new Set();
        this._value = this._stackCall(() => this._getter());
        // Remove this as a dependent constraint from all the previous dependencies
        // that are not used anymore.
        previousDependencies.forEach((pd) => {
            if(!this._dependencies.has(pd)){
                pd._dependentConstraints.delete(this);
            }
        });

        const changed = oldValue !== this._value;
        // If the constraint value has changed, all dependent constraint are invalidated
        // except if they are still being evaluated (self invalidating evaluation if forbidden
        // for now).
        if(changed){
            this._dependentConstraints.forEach((dc) => {
                if(dc._state !== evaluation){
                    dc._setState(invalid);
                }
            });
        }
        this._setState(valid);
        return changed;
    }

    /*
     * Add this constraint to the constraint stack, call f, then
     * removes this constraint from the constraint stack.
     */
    _stackCall(f){
        // Push this constraint to the stack before evaluating it.
        Constraint._constraintStack.push(this);
        const result = f();
        // Removes this constraint from the stack once the evaluation is terminated.
        // It should be at the top of it.
        if(Constraint._constraintStack.pop() !== this){
            //TODO: use a real exception
            throw "Constraint stack error";
        }
        return result;
    }

    _setState(newState){
        if(this._state !== detached){
            this._state = newState;
        }
    }

    /*
     * Register a callback that will be called each time the
     * constraint may be invalid (though its value may not have
     * actually changed).
     *
     * @param check: actually check if the constraint has really
     * changed before calling the callback.
     * This forces the constraint to be re-evaluted each time
     * it may have changed.
     */
    onMayHaveChanged(callback, check=false){
        // FIXME: This is currently required to update the dependencies the first time but it
        // is may not be required *each* time a new handler is added.
        this._update();
        this._mayHaveChangedListeners.push({
            callback: callback,
            check: check
        });
    }

    /*
     * Removes a "may have changed" callback.
     */
    offMayHaveChanged(callback, check){
        const i = this._mayHaveChangedListeners.findIndex(
            (entry) => entry.callback === callback && (
                check == null || entry.check === check
            )
        );
        if(i >= 0){
            this._mayHaveChangedListeners.splice(i, 1);
        }
    }

    _notifyListeners(){
        const initialValue = this._value;
        this._mayHaveChangedListeners.forEach((entry) => {
            if(entry.check){
                if(this._state !== valid){
                    this._update();
                }
                if(this._value !== initialValue){
                    entry.callback.call(null, this);
                }
            } else {
                entry.callback.call(null, this);
            }
        });
    }

    /*
     * Removes this constraint from all its dependencies and removes every change listener.
     * This is useful to make sur the constraint memory can be deallocated.
     * This also condamn the constraint so that it cannot be fetched anymore.
     *
     * Without calling this method, because JS misses weakrefs, a constraint tree is only
     * guaranteed to be garbage collected as a whole, i.e. the memory allocated for any
     * constraint attached to a constraints tree is only guaranteed dealocated when there
     * is no outside reference anymore from any constraint of this tree.
     *
     * Once the constraint is detached, its behavior is not guaranteed.
     */
    detach(){
        // Clear dependencies.
        this._dependencies.forEach(
            (d) => d._dependentConstraints.delete(this)
        );
        this._dependencies.clear();
        // Clear dependent constraints.
        this._dependentConstraints.forEach(
            (d) => d._dependencies.delete(this)
        );
        this._dependentConstraints.clear();
        // Set the state.
        this._setState(detached);
    }

    static _createGetter(getter){
        if(typeof getter === "function"){
            return getter;
        } else if(getter instanceof Constraint) {
            return getter.get.bind(getter);
        } else {
            return () => getter;
        }
    }

    alter (f){
        return new Constraint(() => f(this.get()));
    }
    ifElse(trueVal, falseVal){
        return new Constraint(() => this.get() ? getCtVal(trueVal) : getCtVal(falseVal));
    }
    parseInt(radix){
        return this.alter(x => parseInt(x, getCtVal(radix)));
    }
    parseFloat(){
        return this.alter(x => parseFloat(x));
    }
}

/*
 * Unary operators.
 */
entries({
           not: (x) => !x,
           neg: (x) => -x,
    bitwiseNot: (x) => ~x,
           pos: (x) => +x
}).forEach(([key, op]) => {
    Constraint.prototype[key] = function(){
        return this.alter(x => op(x));
    };
});

/*
 * Operators that can be applied multiple times:
 * e.g constraint.add(x, y, z, ...),
 * constraint.prop('p1', 'p2')
 */
entries({
     add: (x, y) => x + y,
     sub: (x, y) => x - y,
     mul: (x, y) => x * y,
     div: (x, y) => x / y,
    prop: (x, y) => x[y]
}).forEach(([key, op]) => {
    Constraint.prototype[key] = function(...args){
        return new Constraint(
            () => args.reduce((res, x) => op(res, getCtVal(x)), this.get())
        );
    };
});

/*
 * Binary operators.
 */
entries({
                    eq: (x, y) => x === y,
                   neq: (x, y) => x !== y,
               looseEq: (x, y) => x ==  y,
              looseNeq: (x, y) => x !=  y,
                    gt: (x, y) => x  >  y,
                   gte: (x, y) => x >=  y,
                    lt: (x, y) => x  <  y,
                   lte: (x, y) => x <=  y,
                   xor: (x, y) => x  ^  y,
            bitwiseAnd: (x, y) => x  &  y,
             bitwiseOr: (x, y) => x  |  y,
                   mod: (x, y) => x  %  y,
            rightShift: (x, y) => x >>  y,
             leftShift: (x, y) => x <<  y,
    unsignedRightShift: (x, y) => x >>> y,
                   pow: (x, y) => Math.pow(x, y),
                 atan2: (x, y) => Math.atan2(x, y)
}).forEach(([key, op]) => {
    Constraint.prototype[key] = function(y){
        return new Constraint(() => op(this.get(), getCtVal(y)));
    };
});

/*
 * Unary Math functions.
 */
["abs","acos","acosh","asin","asinh","atan","atanh","cbrt","ceil","clz32",
 "cos","cosh","exp","expm1","floor","fround","imul","log","log10","log1p",
 "log2","round","sign","sin","sinh","sqrt","tan","tanh","trunc"].forEach((opName) => {
    Constraint.prototype[opName] = function(){
        return this.alter((x) => Math[opName].call(Math, x));
    };
});

/*
 * Multi args Math functions (added as static methods).
 */
["hypot","max","min"].forEach((opName) => {
    Constraint[opName] = (...args) => new Constraint(() => {
        return Math[opName](...args.map((arg) => getCtVal(arg)))
    });
});

/*
 * Static constraint combinations.
 */
Constraint.sum = (first, ...rest) => new Constraint(() => rest.reduce(
    (acc, x) => acc + getCtVal(x), getCtVal(first)
));
Constraint.product = (first, ...rest) => new Constraint(() => rest.reduce(
    (acc, x) => acc * getCtVal(x), getCtVal(first)
));

Constraint._constraintStack = [];

const getCtVal = (x) => Constraint._createGetter(x).call();
<commit_msg>atan2 is now static.<commit_after>import { entries } from "./utils"

/**
 * Algorythm's principle:
 * - When a constraint switches to the "invalid" or "uncertain" state, its children get the state "uncertain"
 * - When a constraint is evaluated and its value has changed, its children get the state "invalid"
 * - When an "invalid" constraint is retrieved, it is (re)-evaluated
 * - When an "uncertain" constraint is requested, its "invalid" and "uncertain" parents are udpated
 *   - as soon as one "uncertain" or "invalid" parent changed after its evaluation the constraint is
 *       re-evaluated. Because the dependencies are called in order of used, stopping as soon as one changed
 *       dependency is found ensure that we do not trigger the evaluation of a dependency that may
 *       not be used anymore.
 *   - if no parent changed (or all are already valid), the constraint switches to the "valid" state
 *       without being re-evaluated
 */

// Constraint's states:

/*
 * Means the constraint must be re-evaluated, at least one of the constraints it currently
 * depends own is known to have changed.
 */
const invalid = Symbol("invalid");

/*
 * The constraint does not need to be re-evaluated.
 */
const valid = Symbol("valid");

/*
 * It is not sure if the constraint must be re-evaluated. One of its ancestor
 * has been invalidated but it is not sure yet that one of its parents has actually changed. A constraint
 * may still be in the "uncertain" state when all of its parent has been re-evaluated without change
 * but must not be in this state if we know (for sure) that one of its parent has changed.
*/
const uncertain = Symbol("uncertain");

/*
 * The constraint is currently being evaluated. If its value is fetched again (i.e. in the
 * of cyclic dependencies), the cached value will be provided.
 * Because self-invalidating evaluation is forbidden it is not possible to invalidate or
 * set the constraint value when it is in this state (may change in the future).
 */
const evaluation = Symbol("evaluation");

/*
 * The constraint has been detached so that it can be garbaged collected.
 * Any link to its dependencies and the constraint that depends on its has been cut.
 * If fetched, it will now always return `undefined`.
 */
const detached = Symbol("detached");

/*
 * Golden rule: nothing gets out of an instance of this class without registering
 * itself as a dependency of the potential calling's constraint
 * (except if specified otherwise in arguments).
 */
export default class Constraint {

    /*
     * Create a constraint and set its value to *getter*.
     * See set.
     */
    constructor (getter) {
        this._getter = null;
        this._value = null;
        this._state = invalid;
        this._dependencies = new Set();
        this._dependentConstraints = new Set();
        // Registers handlers to be called when the constraint may have changed.
        // Contains an object (see onMayHaveChanged).
        this._mayHaveChangedListeners = [];
        this.set(getter);
    }

    /*
     * Set the constraint value.
     * If it is a function, this function will be used as a getter.
     * If it is another constraint, this constraint will be constrained
     * to this other constraint.
     */
    set(getter) {
        this._getter = Constraint._createGetter(getter);
        this.invalidate();
    }

    /*
     * Invalidate the constraint.
     * As a result, the next its value is fetched, it will be
     * re-evaluated.
     */
    invalidate(){
        if(this._state === evaluation){
            // self invalidating evaluation is forbidden for now.
            // TODO: use a real exception
            throw "Cannot invalidate a constraint still being evaluated."
        }

        this._setState(invalid);
        this._mayHaveChanged();
    }

    /*
     * Notify this constraint that it may have changed.
     * @param alreadyNotified the constraints that have already
     * been notified of this potential change and that should not
     * be notified again.
     */
    _mayHaveChanged(alreadyNotified=new Set()){
        // JS default arguments' evaluation is on method call
        // so default alreadyNotified will be a new set each
        // time _mayHaveChanged() is called without args.

        if(this._state === valid){
            this._setState(uncertain);
        }
        // Notify the on may have change listeners
        this._notifyListeners();
        // Specify that this constraint has been notified
        alreadyNotified.add(this);
        // Notify this constraint's dependent constraint
        // if they have not been yet notified.
        this._dependentConstraints.forEach((dc) => {
            if(!alreadyNotified.has(dc)){
                dc._mayHaveChanged(alreadyNotified);
            }
        });
    }

    /**
     * Get the constraint value, evaluating it if necessary
     * (i.e. if it is invalid).
     */
    get(regAsCallersDependency=true){
        if(this._state === detached) return;
        // If there is a caller, register this constraint as dependency
        // of this caller, and add the caller to the dependent
        // constraints.
        if(regAsCallersDependency){
            this._regAsCallersDependency();
        }

        if(this._state !== valid && this._state !== evaluation){
            this._update();
        }
        return this._value;
    }

    /*
     * Add the constraint as dependency of the constraint on top of the constraint's
     * stack (if any).
     */
    _regAsCallersDependency(){
        if(Constraint._constraintStack.length > 0){
            const caller = Constraint._constraintStack[Constraint._constraintStack.length - 1]
            caller._registerDependency(this);
        }
    }

    /**
     * Register a new constraint on which depends this constraint.
     */
    _registerDependency(dep){
        // This must (and should) not be called if the constraint has been detached.
        this._dependencies.add(dep);
        dep._dependentConstraints.add(this);
    }

    /**
     * Settle the constraint state between valid or invalid if its state is uncertain.
     * This does not update the constraint itself but is likely to update
     * some of its dependencies.
     */
    _settle(){
        if(this._state === uncertain){
            // Update the dependencies (in order) until it finds one that
            // changes.
            let someParentChanged = false;
            for(let p of this._dependencies){
                someParentChanged = p._update();
                if(someParentChanged) break;
            }
            if(!someParentChanged){
                // If no dependency has changed, the present constraint (this) is valid.
                this._setState(valid);
            }
            // If one dependency has changed it should have invalidated this
            // so this._state should be invalid now.
        }
        return this._state;
    }

    /**
     * Update the constraint's value.
     * @returns true if the constraint value has changed, false otherwise.
     */
    _update(){
        // If the state is uncertain, settle it between valid or invalid.
        this._settle();
        if(this._state === invalid){
            return this._evaluate();
        }
        return false;
    }

    _evaluate(){
        this._setState(evaluation);
        const oldValue = this._value;
        // Reset the dependencies (so that dependencies that are not used anymore are removed).
        const previousDependencies = this._dependencies;
        this._dependencies = new Set();
        this._value = this._stackCall(() => this._getter());
        // Remove this as a dependent constraint from all the previous dependencies
        // that are not used anymore.
        previousDependencies.forEach((pd) => {
            if(!this._dependencies.has(pd)){
                pd._dependentConstraints.delete(this);
            }
        });

        const changed = oldValue !== this._value;
        // If the constraint value has changed, all dependent constraint are invalidated
        // except if they are still being evaluated (self invalidating evaluation if forbidden
        // for now).
        if(changed){
            this._dependentConstraints.forEach((dc) => {
                if(dc._state !== evaluation){
                    dc._setState(invalid);
                }
            });
        }
        this._setState(valid);
        return changed;
    }

    /*
     * Add this constraint to the constraint stack, call f, then
     * removes this constraint from the constraint stack.
     */
    _stackCall(f){
        // Push this constraint to the stack before evaluating it.
        Constraint._constraintStack.push(this);
        const result = f();
        // Removes this constraint from the stack once the evaluation is terminated.
        // It should be at the top of it.
        if(Constraint._constraintStack.pop() !== this){
            //TODO: use a real exception
            throw "Constraint stack error";
        }
        return result;
    }

    _setState(newState){
        if(this._state !== detached){
            this._state = newState;
        }
    }

    /*
     * Register a callback that will be called each time the
     * constraint may be invalid (though its value may not have
     * actually changed).
     *
     * @param check: actually check if the constraint has really
     * changed before calling the callback.
     * This forces the constraint to be re-evaluted each time
     * it may have changed.
     */
    onMayHaveChanged(callback, check=false){
        // FIXME: This is currently required to update the dependencies the first time but it
        // is may not be required *each* time a new handler is added.
        this._update();
        this._mayHaveChangedListeners.push({
            callback: callback,
            check: check
        });
    }

    /*
     * Removes a "may have changed" callback.
     */
    offMayHaveChanged(callback, check){
        const i = this._mayHaveChangedListeners.findIndex(
            (entry) => entry.callback === callback && (
                check == null || entry.check === check
            )
        );
        if(i >= 0){
            this._mayHaveChangedListeners.splice(i, 1);
        }
    }

    _notifyListeners(){
        const initialValue = this._value;
        this._mayHaveChangedListeners.forEach((entry) => {
            if(entry.check){
                if(this._state !== valid){
                    this._update();
                }
                if(this._value !== initialValue){
                    entry.callback.call(null, this);
                }
            } else {
                entry.callback.call(null, this);
            }
        });
    }

    /*
     * Removes this constraint from all its dependencies and removes every change listener.
     * This is useful to make sur the constraint memory can be deallocated.
     * This also condamn the constraint so that it cannot be fetched anymore.
     *
     * Without calling this method, because JS misses weakrefs, a constraint tree is only
     * guaranteed to be garbage collected as a whole, i.e. the memory allocated for any
     * constraint attached to a constraints tree is only guaranteed dealocated when there
     * is no outside reference anymore from any constraint of this tree.
     *
     * Once the constraint is detached, its behavior is not guaranteed.
     */
    detach(){
        // Clear dependencies.
        this._dependencies.forEach(
            (d) => d._dependentConstraints.delete(this)
        );
        this._dependencies.clear();
        // Clear dependent constraints.
        this._dependentConstraints.forEach(
            (d) => d._dependencies.delete(this)
        );
        this._dependentConstraints.clear();
        // Set the state.
        this._setState(detached);
    }

    static _createGetter(getter){
        if(typeof getter === "function"){
            return getter;
        } else if(getter instanceof Constraint) {
            return getter.get.bind(getter);
        } else {
            return () => getter;
        }
    }

    alter (f){
        return new Constraint(() => f(this.get()));
    }
    ifElse(trueVal, falseVal){
        return new Constraint(() => this.get() ? getCtVal(trueVal) : getCtVal(falseVal));
    }
    parseInt(radix){
        return this.alter(x => parseInt(x, getCtVal(radix)));
    }
    parseFloat(){
        return this.alter(x => parseFloat(x));
    }
}

/*
 * Unary operators.
 */
entries({
           not: (x) => !x,
           neg: (x) => -x,
    bitwiseNot: (x) => ~x,
           pos: (x) => +x
}).forEach(([key, op]) => {
    Constraint.prototype[key] = function(){
        return this.alter(x => op(x));
    };
});

/*
 * Operators that can be applied multiple times:
 * e.g constraint.add(x, y, z, ...),
 * constraint.prop('p1', 'p2')
 */
entries({
     add: (x, y) => x + y,
     sub: (x, y) => x - y,
     mul: (x, y) => x * y,
     div: (x, y) => x / y,
    prop: (x, y) => x[y]
}).forEach(([key, op]) => {
    Constraint.prototype[key] = function(...args){
        return new Constraint(
            () => args.reduce((res, x) => op(res, getCtVal(x)), this.get())
        );
    };
});

/*
 * Binary operators.
 */
entries({
                    eq: (x, y) => x === y,
                   neq: (x, y) => x !== y,
               looseEq: (x, y) => x ==  y,
              looseNeq: (x, y) => x !=  y,
                    gt: (x, y) => x  >  y,
                   gte: (x, y) => x >=  y,
                    lt: (x, y) => x  <  y,
                   lte: (x, y) => x <=  y,
                   xor: (x, y) => x  ^  y,
            bitwiseAnd: (x, y) => x  &  y,
             bitwiseOr: (x, y) => x  |  y,
                   mod: (x, y) => x  %  y,
            rightShift: (x, y) => x >>  y,
             leftShift: (x, y) => x <<  y,
    unsignedRightShift: (x, y) => x >>> y,
                   pow: (x, y) => Math.pow(x, y)
}).forEach(([key, op]) => {
    Constraint.prototype[key] = function(y){
        return new Constraint(() => op(this.get(), getCtVal(y)));
    };
});

/*
 * Unary Math functions.
 */
["abs","acos","acosh","asin","asinh","atan","atanh","cbrt","ceil","clz32",
 "cos","cosh","exp","expm1","floor","fround","imul","log","log10","log1p",
 "log2","round","sign","sin","sinh","sqrt","tan","tanh","trunc"].forEach((opName) => {
    Constraint.prototype[opName] = function(){
        return this.alter((x) => Math[opName].call(Math, x));
    };
});

/*
 * Multi args Math functions (added as static methods).
 */
["hypot","max","min"].forEach((opName) => {
    Constraint[opName] = (...args) => new Constraint(() => {
        return Math[opName](...args.map((arg) => getCtVal(arg)))
    });
});

/*
 * Static constraint combinations.
 */
Constraint.sum = (first, ...rest) => new Constraint(() => rest.reduce(
    (acc, x) => acc + getCtVal(x), getCtVal(first)
));
Constraint.product = (first, ...rest) => new Constraint(() => rest.reduce(
    (acc, x) => acc * getCtVal(x), getCtVal(first)
));
Constraint.atan2 = (x, y) => new Constraint(() => Math.atan2(getCtVal(x), getCtVal(y)));

Constraint._constraintStack = [];

const getCtVal = (x) => Constraint._createGetter(x).call();
<|endoftext|>


<commit_before>HTMLWidgets.widget({
    name: 'rcytoscapejs',
    type: 'output',

    initialize: function (el, width, height) {
        return {
            // TODO: add instance fields as required
        }
    },

    resize: function (el, width, height, instance) {
        if (instance.cy)
            instance.cy.resize();
    },

    renderValue: function (el, x, instance) {
        //console.log(x.nodeEntries);
        //console.log(x.edgeEntries);

        //var nodetest = JSON.parse(x.nodeEntries);
        //var edgetest = JSON.parse(x.edgeEntries);

        //console.log(nodetest);
        //console.log(edgetest);

        var defaults = ({
          zoomFactor: 0.05, // zoom factor per zoom tick
          zoomDelay: 45, // how many ms between zoom ticks
          minZoom: 0.1, // min zoom level
          maxZoom: 10, // max zoom level
          fitPadding: 50, // padding when fitting
          panSpeed: 10, // how many ms in between pan ticks
          panDistance: 10, // max pan distance per tick
          panDragAreaSize: 75, // the length of the pan drag box in which the vector for panning is calculated (bigger = finer control of pan speed and direction)
          panMinPercentSpeed: 0.25, // the slowest speed we can pan by (as a percent of panSpeed)
          panInactiveArea: 8, // radius of inactive area in pan drag box
          panIndicatorMinOpacity: 0.5, // min opacity of pan indicator (the draggable nib); scales from this to 1.0
          autodisableForMobile: true, // disable the panzoom completely for mobile (since we don't really need it with gestures like pinch to zoom)

          // icon class names
          sliderHandleIcon: 'fa fa-minus',
          zoomInIcon: 'fa fa-plus',
          zoomOutIcon: 'fa fa-minus',
          resetIcon: 'fa fa-expand'
        });

        var positionMap = {};

        //add position information to data for preset layout
        for (var i = 0; i < x.nodeEntries.length ; i++){
            var xPos = x.nodeEntries[i].data.x;
            var yPos = x.nodeEntries[i].data.y;
            positionMap[x.nodeEntries[i].data.id] = {'x':parseFloat(xPos), 'y':parseFloat(yPos)};
        }

        instance.cy = new cytoscape({
            container: el,
            style: cytoscape.stylesheet()
                .selector('node')
                .css({
                    'color': 'data(nodeLabelColor)',
                    'opacity': 'data(opacity)',
                    'content': 'data(name)',
                    'text-valign': 'center',
                    'shape': 'data(shape)',
                    'text-outline-color': 'data(color)',
                    'background-color': 'data(color)',
                    'width': 'data(width)',
                    'height': 'data(height)',
                    'weight': 'data(weight)',
                    'border-style': 'data(borderstyle)',
                    'border-color': 'data(bordercolor)',
                    'border-width': 'data(borderwidth)',
                    'border-opacity': 'data(borderopacity)',
                    'rank': 'data(rank)'
                })
                .selector('edge')
                .css({
                    'line-color': 'data(color)',
                    'source-arrow-color': 'data(color)',
                    'target-arrow-color': 'data(color)',
                    'source-arrow-shape': 'data(edgeSourceShape)',
                    'target-arrow-shape': 'data(edgeTargetShape)',
                    'width': 'data(width)',
                    'opacity': 'data(opacity)'
                })
                .selector(':selected')
                .css({
                    'background-color': '#000000',
                    'line-color': 'black',
                    'target-arrow-color': 'black',
                    'source-arrow-color': 'black'
                })
                .selector('.highlighted')
                .css({
                    'background-color': '#000000',
                    'line-color': '#000000',
                    'target-arrow-color': '#000000',
                    'transition-property': 'background-color, line-color, target-arrow-color',
                    'transition-duration': '0.5s'
                })
                .selector('.faded')
                .css({
                    'opacity': 0.25,
                    'text-opacity': 0
                }),

            elements: {
                nodes: x.nodeEntries,
                //nodes: [{ data: { id:'509209821', name:'509209821', color:'#888888', shape:'ellipse', href:''} }, { data: { id:'531376085', name:'531376085', color:'#888888', shape:'ellipse', href:''} }],
                edges: x.edgeEntries
                //edges: [{ data: { source:'509209821', target:'531376085', color:'#888888', edgeSourceShape:'none', edgeTargetShape:'triangle'} }]

            },
            layout: {
                name: x.layout,
                ungrabifyWhileSimulating: true,
                positions: positionMap
            },
            ready: function () {
                window.cy = this;

                if(x.showPanzoom) {
                  cy.panzoom(defaults);
                }

                cy.boxSelectionEnabled(true);
                cy.userZoomingEnabled(true);
                cy.on('tap', 'node', function (event) {
                    var nodeHighlighted = this.hasClass("highlighted");
                    console.log(nodeHighlighted);
                    var nodes = this.closedNeighborhood().connectedNodes();
                    //console.log(nodes);
                    console.log("A:" + el.id);
                    console.log("ID:" + this._private.data.id);
                    Shiny.onInputChange("clickedNode", this._private.data.id);
                    console.log("break");

                    if (nodes.length === 0) {
                        this.toggleClass("highlighted");
                    }

                    if (nodeHighlighted) {
                        for (var i = 0; i < nodes.length; i++) {
                            if (nodes[i].hasClass("highlighted")) {
                                nodes[i].toggleClass("highlighted");
                            }
                        }
                    } else {
                        for (var i = 0; i < nodes.length; i++) {
                            if (!nodes[i].hasClass("highlighted")) {
                                nodes[i].toggleClass("highlighted");
                            }
                        }
                    }

                    var globalnodes = instance.cy.nodes();
                    var selected = [];
                    for (var i = 0; i < globalnodes.length; i++) {
                        if (globalnodes[i].hasClass("highlighted")) {
                            selected.push(globalnodes[i]._private.ids);
                        }
                    }

                    //console.log(globalnodes);
                    //console.log(selected);

                    var keys = [];
                    for (var i = 0; i < selected.length; i++) {
                        var kk = selected[i];
                        for (var k in kk) keys.push(k);
                    }
                    console.log(keys);
                    Shiny.onInputChange("connectedNodes", keys);
                });

                cy.on('tap', 'node', function (event) {
                    var node = this;
                    $(".qtip").remove();
                    //console.log(event);

                    var name = node._private.data.name;
                    var hrefclick = node._private.data.hrefclick;

                    var target = event.cyTarget;
                    var sourceName = target.data("id");
                    var targetName = target.data("hrefclick");
                    console.log(sourceName);
                    //console.log(targetName);

                    var x = event.cyRenderedPosition.x;
                    var y = event.cyRenderedPosition.y;
                    //var x = event.cyPosition.x;
                    //var y = event.cyPosition.y;
                    //console.log("x="+x+" Y="+y);

                    cy.getElementById(node.id()).qtip({
                        content: {
                            text: function (event, api) {
                              // Retrieve content from custom attribute of the $('.selector') elements.
                              return hrefclick;
                            }
                        },
                        show: {
                            ready: true
                        },
                        position: {
                            my: 'bottom center',
                            at: 'top center',
                            adjust: {
                              cyViewport: true
                            },
                            effect: false
                        },
                        hide: false,
                        style: {
                            classes: 'qtip-bootstrap',
                            tip: {
                              width: 48,
                              height: 8
                            }
                        }
                    });
                });
                cy.on('mouseover', 'node', function (event) {
                    var node = this;
                    //$(".qtip").remove();
                    //console.log(event);

                    var name = node._private.data.name;
                    var href = node._private.data.href;

                    var target = event.cyTarget;
                    var sourceName = target.data("id");
                    var targetName = target.data("href");
                    console.log(sourceName);
                    //console.log(targetName);

                    var x = event.cyRenderedPosition.x;
                    var y = event.cyRenderedPosition.y;
                    //var x = event.cyPosition.x;
                    //var y = event.cyPosition.y;
                    //console.log("x="+x+" Y="+y);

                    cy.getElementById(node.id()).qtip({
                        content: {
                            text: function (event, api) {
                              // Retrieve content from custom attribute of the $('.selector') elements.
                              return href;
                            }
                        },
                        show: {
                            ready: true,
                            effect: false,
                            delay: 500
                        },
                        position: {
                            my: 'bottom center',
                            at: 'top center',
                            adjust: {
                              cyViewport: true
                            },
                            effect: false
                        },
                        hide: {
                            fixed: true,
                            event: 'mouseleave',
                            effect: false
                        },
                        style: {
                            classes: 'qtip-bootstrap',
                            tip: {
                              width: 16,
                              height: 8
                            }
                        }
                    });
                });
            }
        });
    }
});
<commit_msg>Add "solo: true" to show<commit_after>HTMLWidgets.widget({
    name: 'rcytoscapejs',
    type: 'output',

    initialize: function (el, width, height) {
        return {
            // TODO: add instance fields as required
        }
    },

    resize: function (el, width, height, instance) {
        if (instance.cy)
            instance.cy.resize();
    },

    renderValue: function (el, x, instance) {
        //console.log(x.nodeEntries);
        //console.log(x.edgeEntries);

        //var nodetest = JSON.parse(x.nodeEntries);
        //var edgetest = JSON.parse(x.edgeEntries);

        //console.log(nodetest);
        //console.log(edgetest);

        var defaults = ({
          zoomFactor: 0.05, // zoom factor per zoom tick
          zoomDelay: 45, // how many ms between zoom ticks
          minZoom: 0.1, // min zoom level
          maxZoom: 10, // max zoom level
          fitPadding: 50, // padding when fitting
          panSpeed: 10, // how many ms in between pan ticks
          panDistance: 10, // max pan distance per tick
          panDragAreaSize: 75, // the length of the pan drag box in which the vector for panning is calculated (bigger = finer control of pan speed and direction)
          panMinPercentSpeed: 0.25, // the slowest speed we can pan by (as a percent of panSpeed)
          panInactiveArea: 8, // radius of inactive area in pan drag box
          panIndicatorMinOpacity: 0.5, // min opacity of pan indicator (the draggable nib); scales from this to 1.0
          autodisableForMobile: true, // disable the panzoom completely for mobile (since we don't really need it with gestures like pinch to zoom)

          // icon class names
          sliderHandleIcon: 'fa fa-minus',
          zoomInIcon: 'fa fa-plus',
          zoomOutIcon: 'fa fa-minus',
          resetIcon: 'fa fa-expand'
        });

        var positionMap = {};

        //add position information to data for preset layout
        for (var i = 0; i < x.nodeEntries.length ; i++){
            var xPos = x.nodeEntries[i].data.x;
            var yPos = x.nodeEntries[i].data.y;
            positionMap[x.nodeEntries[i].data.id] = {'x':parseFloat(xPos), 'y':parseFloat(yPos)};
        }

        instance.cy = new cytoscape({
            container: el,
            style: cytoscape.stylesheet()
                .selector('node')
                .css({
                    'color': 'data(nodeLabelColor)',
                    'opacity': 'data(opacity)',
                    'content': 'data(name)',
                    'text-valign': 'center',
                    'shape': 'data(shape)',
                    'text-outline-color': 'data(color)',
                    'background-color': 'data(color)',
                    'width': 'data(width)',
                    'height': 'data(height)',
                    'weight': 'data(weight)',
                    'border-style': 'data(borderstyle)',
                    'border-color': 'data(bordercolor)',
                    'border-width': 'data(borderwidth)',
                    'border-opacity': 'data(borderopacity)',
                    'rank': 'data(rank)'
                })
                .selector('edge')
                .css({
                    'line-color': 'data(color)',
                    'source-arrow-color': 'data(color)',
                    'target-arrow-color': 'data(color)',
                    'source-arrow-shape': 'data(edgeSourceShape)',
                    'target-arrow-shape': 'data(edgeTargetShape)',
                    'width': 'data(width)',
                    'opacity': 'data(opacity)'
                })
                .selector(':selected')
                .css({
                    'background-color': '#000000',
                    'line-color': 'black',
                    'target-arrow-color': 'black',
                    'source-arrow-color': 'black'
                })
                .selector('.highlighted')
                .css({
                    'background-color': '#000000',
                    'line-color': '#000000',
                    'target-arrow-color': '#000000',
                    'transition-property': 'background-color, line-color, target-arrow-color',
                    'transition-duration': '0.5s'
                })
                .selector('.faded')
                .css({
                    'opacity': 0.25,
                    'text-opacity': 0
                }),

            elements: {
                nodes: x.nodeEntries,
                //nodes: [{ data: { id:'509209821', name:'509209821', color:'#888888', shape:'ellipse', href:''} }, { data: { id:'531376085', name:'531376085', color:'#888888', shape:'ellipse', href:''} }],
                edges: x.edgeEntries
                //edges: [{ data: { source:'509209821', target:'531376085', color:'#888888', edgeSourceShape:'none', edgeTargetShape:'triangle'} }]

            },
            layout: {
                name: x.layout,
                ungrabifyWhileSimulating: true,
                positions: positionMap
            },
            ready: function () {
                window.cy = this;

                if(x.showPanzoom) {
                  cy.panzoom(defaults);
                }

                cy.boxSelectionEnabled(true);
                cy.userZoomingEnabled(true);
                cy.on('tap', 'node', function (event) {
                    var nodeHighlighted = this.hasClass("highlighted");
                    console.log(nodeHighlighted);
                    var nodes = this.closedNeighborhood().connectedNodes();
                    //console.log(nodes);
                    console.log("A:" + el.id);
                    console.log("ID:" + this._private.data.id);
                    Shiny.onInputChange("clickedNode", this._private.data.id);
                    console.log("break");

                    if (nodes.length === 0) {
                        this.toggleClass("highlighted");
                    }

                    if (nodeHighlighted) {
                        for (var i = 0; i < nodes.length; i++) {
                            if (nodes[i].hasClass("highlighted")) {
                                nodes[i].toggleClass("highlighted");
                            }
                        }
                    } else {
                        for (var i = 0; i < nodes.length; i++) {
                            if (!nodes[i].hasClass("highlighted")) {
                                nodes[i].toggleClass("highlighted");
                            }
                        }
                    }

                    var globalnodes = instance.cy.nodes();
                    var selected = [];
                    for (var i = 0; i < globalnodes.length; i++) {
                        if (globalnodes[i].hasClass("highlighted")) {
                            selected.push(globalnodes[i]._private.ids);
                        }
                    }

                    //console.log(globalnodes);
                    //console.log(selected);

                    var keys = [];
                    for (var i = 0; i < selected.length; i++) {
                        var kk = selected[i];
                        for (var k in kk) keys.push(k);
                    }
                    console.log(keys);
                    Shiny.onInputChange("connectedNodes", keys);
                });

                cy.on('tap', 'node', function (event) {
                    var node = this;
                    $(".qtip").remove();
                    //console.log(event);

                    var name = node._private.data.name;
                    var hrefclick = node._private.data.hrefclick;

                    var target = event.cyTarget;
                    var sourceName = target.data("id");
                    var targetName = target.data("hrefclick");
                    console.log(sourceName);
                    //console.log(targetName);

                    var x = event.cyRenderedPosition.x;
                    var y = event.cyRenderedPosition.y;
                    //var x = event.cyPosition.x;
                    //var y = event.cyPosition.y;
                    //console.log("x="+x+" Y="+y);

                    cy.getElementById(node.id()).qtip({
                        content: {
                            text: function (event, api) {
                              // Retrieve content from custom attribute of the $('.selector') elements.
                              return hrefclick;
                            }
                        },
                        show: {
                            ready: true
                        },
                        position: {
                            my: 'bottom center',
                            at: 'top center',
                            adjust: {
                              cyViewport: true
                            },
                            effect: false
                        },
                        hide: false,
                        style: {
                            classes: 'qtip-bootstrap',
                            tip: {
                              width: 48,
                              height: 8
                            }
                        }
                    });
                });
                cy.on('mouseover', 'node', function (event) {
                    var node = this;
                    //$(".qtip").remove();
                    //console.log(event);

                    var name = node._private.data.name;
                    var href = node._private.data.href;

                    var target = event.cyTarget;
                    var sourceName = target.data("id");
                    var targetName = target.data("href");
                    console.log(sourceName);
                    //console.log(targetName);

                    var x = event.cyRenderedPosition.x;
                    var y = event.cyRenderedPosition.y;
                    //var x = event.cyPosition.x;
                    //var y = event.cyPosition.y;
                    //console.log("x="+x+" Y="+y);

                    cy.getElementById(node.id()).qtip({
                        content: {
                            text: function (event, api) {
                              // Retrieve content from custom attribute of the $('.selector') elements.
                              return href;
                            }
                        },
                        show: {
                            ready: true
                            effect: false,
                            delay: 500,
                            solo: true
                        },
                        position: {
                            my: 'bottom center',
                            at: 'top center',
                            adjust: {
                              cyViewport: true
                            },
                            effect: false
                        },
                        hide: {
                            fixed: true,
                            event: 'mouseleave',
                            effect: false
                        },
                        style: {
                            classes: 'qtip-bootstrap',
                            tip: {
                              width: 16,
                              height: 8
                            }
                        }
                    });
                });
            }
        });
    }
});
<|endoftext|>