<commit_before>var through = require('through2')
  , async = require('async')
  , powersetStream = require('powerset-stream')
  , _ = require('lodash')
  , natural = require('natural')
  , dm = natural.DoubleMetaphone
  , combineWords = require('../combine-words')

module.exports = function (client, res, msg, cb) {
  var blacklisted = []
    , phoneticCache = []
    , joinStream = through.obj(function (chunk, encoding, callback) {
        this.push(chunk.join(''))

        callback()
      })
    , wordWhitelist = function (word, encoding, callback) {
        var self = this

        client.whitelist.words.contains(word, function (error, exists) {
          if (error) return callback(error)
          if (!exists) self.push(word)

          callback()
        })
      }
    , wordBlacklist = function (isEnd) {
        return function (word, encoding, callback) {
          var self = this

          client.blacklist.words.contains(word, function (error, exists) {
            if (error) return callback(error)
            if (!exists) {
              if (!isEnd) self.push(word)

              return callback()
            }

            // Check if bypass
            client.blacklist.wordsBypass.get(word, function (error, assoc) {
              if (error) return callback(error)

              if (assoc) {
                blacklisted.push(assoc)
              } else {
                blacklisted.push(word)
              }

              callback()
            })

          })
        }
      }
    , wordCombinationStream = through.obj(function (word, encoding, callback) {
        if (word.length < 3) return callback()

        var self = this
          , letters = word.split('')

        combineWords(letters, function (newWord, cb) {
          self.push(newWord)

          cb()
        }, callback)

      })
    , distanceCheck = function (word, encoding, callback) {
        var self = this
          , found = false

        function compareWords(badWord) {
          if (found) return

          var similarity = natural.JaroWinklerDistance(badWord, word)

          if (similarity >= 0.87) {
            blacklisted.push(badWord)
            found = true
          }
        }

        client.blacklist.words.stream({ count: 20 })
          .on('data', function (data) {
            if (Array.isArray(data)) {
              data.forEach(compareWords)
            } else {
              compareWords(data)
            }
          }).on('end', function () {
            if (!found) self.push(word)

            callback()
          })
      }
    , phoneticCheck = function (word, encoding, callback) {
        var self = this
          , phonetics = dm.process(word)

        if (!phonetics[0] || !phonetics[1]) return callback()

        if (phonetics[0] === phonetics[1] && phoneticCache.indexOf(phonetics[0]) !== -1) return callback()
        if (phoneticCache.indexOf(phonetics[0]) !== -1) return callback()
        if (phoneticCache.indexOf(phonetics[1]) !== -1) return callback()

        // Remove duplicate phonetic letters to avoid misses, this might introduce false positives, to test
        phonetics = phonetics.map(function (phonetic) {
          return phonetic.replace(/([A-Z])\1{2,}/, '$1')
        })

        if (phonetics[0] === phonetics[1]) {
          phoneticCache.push(phonetics[0])
          phonetics = [ phonetics[0] ]
        } else {
          phoneticCache.push(phonetics[0], phonetics[1])
        }

        var found = false

        client.blacklist.phonetics.get(phonetics, function (error, data) {
          if (error) return callback(error)

          if (data && data.length !== 0) {
            if (data[0] && blacklisted.indexOf(data[0]) === -1) {
              blacklisted.push(data[0])
              found = true
            }

            if (data[1] && blacklisted.indexOf(data[1]) === -1) {
             blacklisted.push(data[1])
             found = true
           }
          }

          if (!found) self.push(word)

          callback()
        })
      }
    , words = msg.match(/\S+/g).filter(function (item, i, allItems) {
          return i === allItems.indexOf(item)
        }).map(function (word) {
          return word.toLowerCase()
        }).map(function (word) {
          return word.replace(/[\W_]+/g, ' ').split(' ')
        })
    , wordBlacklistStream = through.obj(wordBlacklist(true))
    , uppercaseBypass = msg.match(/([A-Z]+)/g)

  words = _.flatten(words)

  wordBlacklistStream.on('finish', function () {
    res.words = _.unique(blacklisted)
    cb(null, msg)
  })

  if (uppercaseBypass && uppercaseBypass.length !== 0) {
    words.push(uppercaseBypass.join('').toLowerCase())
  }

  async.waterfall(
    [ function (callback) {
        // First find blacklist combinations
        combineWords(words, function (newWord, cb) {
          client.blacklist.words.contains(newWord, function (error, exists) {
            if (error) return cb(error)
            // TODO Remove the combinations from 'words'
            if (exists) {
              client.blacklist.wordsBypass.get(newWord, function (error, assoc) {
                if (error) return callback(error)

                if (assoc) {
                  blacklisted.push(assoc)
                } else {
                  blacklisted.push(newWord)
                }

                cb()
              })
            } else {
              cb()
            }
          })
        }, function (error) {
          if (error) return callback(error)

          callback(null, words)
        })
      }
    , function (words, callback) {
        async.reject(words, function (word, rejectCb) {
          // Ignore single letter words
          if (word.length === 1) return rejectCb(false)

          client.whitelist.words.contains(word, function (error, exists) {
            if (error) return rejectCb(false)

            rejectCb(exists)
          })
        }, function (words) {
          callback(null, words)
        })
      }
    , function (words, callback) {
        async.reject(words, function (word, rejectCb) {
          client.blacklist.words.contains(word, function (error, exists) {
            if (error) return rejectCb(false)
            if (!exists) return rejectCb(exists)

            client.blacklist.wordsBypass.get(word, function (error, assoc) {
              if (error) return callback(error)

              if (assoc) {
                blacklisted.push(assoc)
              } else {
                blacklisted.push(word)
              }

              rejectCb(exists)
            })
          })
        }, function (words) {
          callback(null, words)
        })
      }
    ], function (error, words) {
        if (error) return cb(error)

        if (words.length === 0) {
          res.words = _.unique(blacklisted)
          return cb(null, msg)
        }

        powersetStream(words)
          .pipe(joinStream)
          .pipe(through.obj(wordWhitelist))
          .pipe(through.obj(wordBlacklist(false)))
          .pipe(through.obj(phoneticCheck))
          .pipe(through.obj(distanceCheck))
          .pipe(wordCombinationStream)
          .pipe(through.obj(wordWhitelist))
          .pipe(wordBlacklistStream)
      }
    )

}
<commit_msg>Uppercase check optimisations<commit_after>var through = require('through2')
  , async = require('async')
  , powersetStream = require('powerset-stream')
  , _ = require('lodash')
  , natural = require('natural')
  , dm = natural.DoubleMetaphone
  , combineWords = require('../combine-words')

module.exports = function (client, res, msg, cb) {
  var blacklisted = []
    , phoneticCache = []
    , joinStream = through.obj(function (chunk, encoding, callback) {
        this.push(chunk.join(''))

        callback()
      })
    , wordWhitelist = function (word, encoding, callback) {
        var self = this

        client.whitelist.words.contains(word, function (error, exists) {
          if (error) return callback(error)
          if (!exists) self.push(word)

          callback()
        })
      }
    , wordBlacklist = function (isEnd) {
        return function (word, encoding, callback) {
          var self = this

          client.blacklist.words.contains(word, function (error, exists) {
            if (error) return callback(error)
            if (!exists) {
              if (!isEnd) self.push(word)

              return callback()
            }

            // Check if bypass
            client.blacklist.wordsBypass.get(word, function (error, assoc) {
              if (error) return callback(error)

              if (assoc) {
                blacklisted.push(assoc)
              } else {
                blacklisted.push(word)
              }

              callback()
            })

          })
        }
      }
    , wordCombinationStream = through.obj(function (word, encoding, callback) {
        if (word.length < 3) return callback()

        var self = this
          , letters = word.split('')

        combineWords(letters, function (newWord, cb) {
          self.push(newWord)

          cb()
        }, callback)

      })
    , distanceCheck = function (word, encoding, callback) {
        var self = this
          , found = false

        function compareWords(badWord) {
          if (found) return

          var similarity = natural.JaroWinklerDistance(badWord, word)

          if (similarity >= 0.87) {
            blacklisted.push(badWord)
            found = true
          }
        }

        client.blacklist.words.stream({ count: 20 })
          .on('data', function (data) {
            if (Array.isArray(data)) {
              data.forEach(compareWords)
            } else {
              compareWords(data)
            }
          }).on('end', function () {
            if (!found) self.push(word)

            callback()
          })
      }
    , phoneticCheck = function (word, encoding, callback) {
        var self = this
          , phonetics = dm.process(word)

        if (!phonetics[0] || !phonetics[1]) return callback()

        if (phonetics[0] === phonetics[1] && phoneticCache.indexOf(phonetics[0]) !== -1) return callback()
        if (phoneticCache.indexOf(phonetics[0]) !== -1) return callback()
        if (phoneticCache.indexOf(phonetics[1]) !== -1) return callback()

        // Remove duplicate phonetic letters to avoid misses, this might introduce false positives, to test
        phonetics = phonetics.map(function (phonetic) {
          return phonetic.replace(/([A-Z])\1{2,}/, '$1')
        })

        if (phonetics[0] === phonetics[1]) {
          phoneticCache.push(phonetics[0])
          phonetics = [ phonetics[0] ]
        } else {
          phoneticCache.push(phonetics[0], phonetics[1])
        }

        var found = false

        client.blacklist.phonetics.get(phonetics, function (error, data) {
          if (error) return callback(error)

          if (data && data.length !== 0) {
            if (data[0] && blacklisted.indexOf(data[0]) === -1) {
              blacklisted.push(data[0])
              found = true
            }

            if (data[1] && blacklisted.indexOf(data[1]) === -1) {
             blacklisted.push(data[1])
             found = true
           }
          }

          if (!found) self.push(word)

          callback()
        })
      }
    , words = msg.match(/\S+/g).filter(function (item, i, allItems) {
          return i === allItems.indexOf(item)
        }).map(function (word) {
          return word.toLowerCase()
        }).map(function (word) {
          return word.replace(/[\W_]+/g, ' ').split(' ')
        })
    , wordBlacklistStream = through.obj(wordBlacklist(true))
    , uppercaseBypass = msg.match(/([A-Z]+)/g)

  words = _.flatten(words)

  wordBlacklistStream.on('finish', function () {
    res.words = _.unique(blacklisted)
    cb(null, msg)
  })

  if (uppercaseBypass && uppercaseBypass.length !== words.length) {
    var upperWord = uppercaseBypass.join('').toLowerCase()

    if (words.indexOf(upperWord) === -1) words.push(upperWord)
  }

  async.waterfall(
    [ function (callback) {
        // First find blacklist combinations
        combineWords(words, function (newWord, cb) {
          client.blacklist.words.contains(newWord, function (error, exists) {
            if (error) return cb(error)
            // TODO Remove the combinations from 'words'
            if (exists) {
              client.blacklist.wordsBypass.get(newWord, function (error, assoc) {
                if (error) return callback(error)

                if (assoc) {
                  blacklisted.push(assoc)
                } else {
                  blacklisted.push(newWord)
                }

                cb()
              })
            } else {
              cb()
            }
          })
        }, function (error) {
          if (error) return callback(error)

          callback(null, words)
        })
      }
    , function (words, callback) {
        async.reject(words, function (word, rejectCb) {
          // Ignore single letter words
          if (word.length === 1) return rejectCb(false)

          client.whitelist.words.contains(word, function (error, exists) {
            if (error) return rejectCb(false)

            rejectCb(exists)
          })
        }, function (words) {
          callback(null, words)
        })
      }
    , function (words, callback) {
        async.reject(words, function (word, rejectCb) {
          client.blacklist.words.contains(word, function (error, exists) {
            if (error) return rejectCb(false)
            if (!exists) return rejectCb(exists)

            client.blacklist.wordsBypass.get(word, function (error, assoc) {
              if (error) return callback(error)

              if (assoc) {
                blacklisted.push(assoc)
              } else {
                blacklisted.push(word)
              }

              rejectCb(exists)
            })
          })
        }, function (words) {
          callback(null, words)
        })
      }
    ], function (error, words) {
        if (error) return cb(error)

        if (words.length === 0) {
          res.words = _.unique(blacklisted)
          return cb(null, msg)
        }

        powersetStream(words)
          .pipe(joinStream)
          .pipe(through.obj(wordWhitelist))
          .pipe(through.obj(wordBlacklist(false)))
          .pipe(through.obj(phoneticCheck))
          .pipe(through.obj(distanceCheck))
          .pipe(wordCombinationStream)
          .pipe(through.obj(wordWhitelist))
          .pipe(wordBlacklistStream)
      }
    )

}
<|endoftext|>


<commit_before>if(typeof process !== "undefined"){
	var requirejs = require('requirejs');
	var define = requirejs.define;
}

define(function(require, exports, module){
//	The Globalization class in Noodles is designed to allow warnings, errors, tags, and miscellaneous identifiers
//to be translated dynamically into other languages. This allows templates of other languages to be created.
//	The Class relies on a particular data structure being created as a dictionary whose keys are english
//allowing a translation into the target language to occur. This, unfortunately, means  that all plugins
//need to be written in English if they are expected to work globally. If the Globalization Class is not used,
//the original language that warnings, error, etc are written in wil always be the text that is passed for
//those particular messages.

/*--module--
name:currentLanguages
description: list of lanugages currently available.
@type{object}
*/
var currentLanguages = {};
/*--module--
name:LanguageClasses
description: hash of saved classes
@type{object}
*/
var languageClasses = {}
/*--exports--
name:language
description: function that returns the appropriate class
@param{string}
@param{function}
*/
exports.language = function(lang,callback){
	if(typeof lang !== "string") lang = 'english';
	lang = lang.toLowerCase();
	if(!currentLanguages[this.language]){
		var _self = this;
		if(lang === "english"){
			currentLanguages[lang] = true;
			languageClasses[lang] = new Globalization(lang,{});
			callback(languageClasses[lang]);
		}
		else{
			require(['./../globalization/' + lang],function(lanuage){
				currentLanguages[lang] = true;
				languageClasses[lang] = new Globalization(lang,language);
				callback(languageClasses[lang]);
			});
		}
	}
	else{
		callback(languageClasses[lang]);
	}
};
/*--exports--
name:addToLanguage
description: function adds warnings, tags, errors, etc
	to specified language
@param{string}
@param{object}
*/
exports.addToLanguage = function(language,languageObject){
	language = language.toString();
	language = languageClasses[language];
	var keys = Object.keys(languageObject),
		i = keys.length,
		keys2, i2,temp,temp2;
	while(i-- !== 0){
		temp2 = languageObject[keys[i]];
		keys2 = Object.keys(temp2);
		i2 = keys2.length;
		temp = language[keys[i]];
		while(i2-- !== 0){
			temp[keys2[i2].toLowerCase()] = temp2[keys2[i2]];
		}
	}
};
/*--module--
name:Language
description:Language Class.
*/
var Globalization = function(lang,language){
	this.language = lang;
	if(this.language !== 'english'){
		var keys = Object.keys(language),
			i = keys.length;
		while(i-- !== 0){
			this[keys[i]] = language[keys[i]];
		}
	}
}
/*--Globalization--
name:warning
description:translate warnings
*/
Globalization.prototype.warning = function(string){
	if(typeof string !== "string") return "";
	if(this.language === "english") return string;

	return this.warnings[string.toLowerCase()] || string || "";
};
/*--Globalization--
name:error
description:translate warnings
*/
Globalization.prototype.error = function(string){
	if(typeof string !== "string") return "";
	if(this.language === "english") return string;

	return this.errors[string.toLowerCase()] || string || "";
};
/*--Globalization--
name:tag
description:translate warnings
*/
Globalization.prototype.tag = function(string){
	if(typeof string !== "string") return "";
	if(this.language === "english") return string;

	return this.tags[string.toLowerCase()] || string || "";
};
/*--Globalization--
name:error
description:translate warnings
*/
Globalization.prototype.other = function(string){
	if(typeof string !== "string") return "";
	if(this.language === "english") return string;

	return this.others[string.toLowerCase()] || string || "";
};
/*--Globalization--
name:plugin
description:translate pluginName
*/
Globalization.prototype.plugin = function(string){
	if(typeof string !== "string") return "";
	if(this.language === "english") return string;

	return this.plugin[string.toLowerCase()] || string || "";
};
/*--Globalization--
name:coreTags
description:coreTags in english
*/
Globalization.prototype.coreTags = require('./config').coretags;
});<commit_msg>added support for multiple argument translations<commit_after>if(typeof process !== "undefined"){
	var requirejs = require('requirejs');
	var define = requirejs.define;
}

define(function(require, exports, module){
//	The Globalization class in Noodles is designed to allow warnings, errors, tags, and miscellaneous identifiers
//to be translated dynamically into other languages. This allows templates of other languages to be created.
//	The Class relies on a particular data structure being created as a dictionary whose keys are english
//allowing a translation into the target language to occur. This, unfortunately, means  that all plugins
//need to be written in English if they are expected to work globally. If the Globalization Class is not used,
//the original language that warnings, error, etc are written in wil always be the text that is passed for
//those particular messages.

/*--module--
name:currentLanguages
description: list of lanugages currently available.
@type{object}
*/
var currentLanguages = {};
/*--module--
name:LanguageClasses
description: hash of saved classes
@type{object}
*/
var languageClasses = {}
/*--exports--
name:language
description: function that returns the appropriate class
@param{string}
@param{function}
*/
exports.language = function(lang,callback){
	if(typeof lang !== "string") lang = 'english';
	lang = lang.toLowerCase();
	if(!currentLanguages[this.language]){
		var _self = this;
		if(lang === "english"){
			currentLanguages[lang] = true;
			languageClasses[lang] = new Globalization(lang,{});
			callback(languageClasses[lang]);
		}
		else{
			require(['./../globalization/' + lang],function(lanuage){
				currentLanguages[lang] = true;
				languageClasses[lang] = new Globalization(lang,language);
				callback(languageClasses[lang]);
			});
		}
	}
	else{
		callback(languageClasses[lang]);
	}
};
/*--exports--
name:addToLanguage
description: function adds warnings, tags, errors, etc
	to specified language
@param{string}
@param{object}
*/
exports.addToLanguage = function(language,languageObject){
	language = language.toString();
	language = languageClasses[language];
	var keys = Object.keys(languageObject),
		i = keys.length,
		keys2, i2,temp,temp2;
	while(i-- !== 0){
		temp2 = languageObject[keys[i]];
		keys2 = Object.keys(temp2);
		i2 = keys2.length;
		temp = language[keys[i]];
		while(i2-- !== 0){
			temp[keys2[i2].toLowerCase()] = temp2[keys2[i2]];
		}
	}
};
/*--module--
name:Language
description:Language Class.
*/
var Globalization = function(lang,language){
	this.language = lang;
	if(this.language !== 'english'){
		var keys = Object.keys(language),
			i = keys.length;
		while(i-- !== 0){
			this[keys[i]] = language[keys[i]];
		}
	}
}
/*--Globalization--
name:warning
description:translate warnings
*/
Globalization.prototype.warning = function(string){
	if(typeof string !== "string") return "";
	if(this.language === "english") return string;
	var args = Array.prototype.slice.call(arguments);
	args.shift();
	return (this.warnings[string.toLowerCase()] || string || "") + args.join('');
};
/*--Globalization--
name:error
description:translate warnings
*/
Globalization.prototype.error = function(string){
	if(typeof string !== "string") return "";
	if(this.language === "english") return string;
	var args = Array.prototype.slice.call(arguments);
	args.shift();
	return (this.errors[string.toLowerCase()] || string || "") + args.join('');;
};
/*--Globalization--
name:tag
description:translate warnings
*/
Globalization.prototype.tag = function(string){
	if(typeof string !== "string") return "";
	if(this.language === "english") return string;
	var args = Array.prototype.slice.call(arguments);
	args.shift();
	return (this.tags[string.toLowerCase()] || string || "") + args.join('');
};
/*--Globalization--
name:error
description:translate warnings
*/
Globalization.prototype.other = function(string){
	if(typeof string !== "string") return "";
	if(this.language === "english") return string;
	var args = Array.prototype.slice.call(arguments);
	args.shift();
	return (this.others[string.toLowerCase()] || string || "") + args.join('');
};
/*--Globalization--
name:plugin
description:translate pluginName
*/
Globalization.prototype.plugin = function(string){
	if(typeof string !== "string") return "";
	if(this.language === "english") return string;
	var args = Array.prototype.slice.call(arguments);
	args.shift();
	return (this.plugin[string.toLowerCase()] || string || "") + args.join('');
};
/*--Globalization--
name:coreTags
description:coreTags in english
*/
Globalization.prototype.coreTags = require('./config').coretags;
});<|endoftext|>


<commit_before>var Juttle = require('juttle/lib/runtime').Juttle;
var JuttleErrors = require('juttle/lib/errors');
var JuttleMoment = require('juttle/lib/moment/juttle-moment');
var Promise = require('bluebird');
var google = require('googleapis');
var googleAuth = require('google-auth-library');
var _ = require('underscore');
var Batchelor = require('batchelor');
var FilterGmailCompiler = require('./filter-gmail-compiler');

var auth;

var Read = Juttle.proc.base.extend({
    procName: 'read-gmail',

    initialize: function(options, params, pname, location, program, juttle) {
        this.logger.debug('intitialize', options, params);
        this.gmail = google.gmail('v1');

        var time_related_options = ['from', 'to', 'last'];
        var allowed_options = time_related_options.concat(['raw']);
        var unknown = _.difference(_.keys(options), allowed_options);

        if (unknown.length > 0) {
            throw this.compile_error('RT-UNKNOWN-OPTION-ERROR',
                                     {proc: 'read gmail', option: unknown[0]});
        }

        // One of 'from', 'to', or 'last' must be present.
        var opts = _.intersection(_.keys(options), time_related_options);
        if (opts.length === 0) {
            // Waiting on a juttle release that incorporates https://github.com/juttle/juttle/pull/108
            //throw this.compile_error('RT-MISSING-TIME-RANGE-ERROR');
            throw new Error('Error: One of -from, -to, or -last must be specified to define a query time range');
        }

        // If 'from'/'to' are present, 'last' can not be present.
        if ((_.has(options, 'from') || _.has(options, 'to')) &&
            _.has(options, 'last')) {
            throw this.compile_error('RT-LAST-FROM-TO-ERROR');
        }

        // 'from' must be before 'to'
        if (_.has(options, 'from') && _.has(options, 'to') &&
            options.from > options.to) {
            throw this.compile_error('RT-TO-FROM-MOMENT-ERROR');
        }

        // If 'last' is specified, set appropriate 'from'/'to'
        if (_.has(options, 'last')) {
            this.from = program.now.subtract(options.last);
            this.to = program.now;
        } else {
            // Initialize from/to if necessary.
            this.from = options.from || program.now;
            this.to = options.to || program.now;
        }

        this.raw = options.raw;
        this.filter_search_expr = undefined;

        if (params.filter_ast) {
            this.logger.debug("Filter ast: ", params.filter_ast);
            var compiler = new FilterGmailCompiler({location: location});
            this.filter_search_expr = compiler.compile(params.filter_ast);
            this.logger.debug("Filter expression: ", this.filter_search_expr);
        }

        this.delay = options.delay || JuttleMoment.duration(1, 's');

        Promise.promisifyAll(this.gmail.users.messages);
    },

    start: function() {
        var self = this;

        return self.get_messages_for_timerange(self.from, self.to);
    },

    teardown: function() {
    },

    // Non juttle proc methods below here
    get_messages_for_timerange: function(from, to) {
        this.logger.debug('get_messages_for_timerange from=' + from + " to=" + to);
        var self = this;

        // Build a search expression given the options.
        var search = self.raw || "";

        if (this.filter_search_expr !== undefined) {
            search += " " + this.filter_search_expr;
        }

        var now = new JuttleMoment();

        // The gmail search expression only has per-day granularity,
        // so we quantize the -from and -to to the nearest day and do
        // additional filtering in get_messages().

        // XXX/mstemm not sure how the time zone is set. It's definitely not in UTC.
        search += " after:" + JuttleMoment.format(from, "YYYY/MM/DD", "US/Pacific");

        // Add 1 day to to and then quantize to a day. This
        // results in the next day, such that the before: date is
        // always in the future.
        if (! self.to.isEnd()) {
            search += " before:" + JuttleMoment.format(to.add(JuttleMoment.duration(1, "d")), "YYYY/MM/DD", "US/Pacific");
        }

        this.logger.debug("Search string:", search);
        return self.get_messages(from, to, search)
        .call("sort", function(a, b) {
            if (a.time.lt(b.time)) {
                return -1;
            } else if (a.time.eq(b.time)) {
                return 0;
            } else {
                return 1;
            }
        })
        .then(function(messages) {
            if (messages && messages.length > 0) {
                self.emit(messages);
            }
            return messages;
        })
        .then(function(messages) {
            // If to is in the future, arrange with the scheduler to
            // get all messages from the timestamp of the last message
            // to "to". Otherwise we're done.
            if (to.gt(now)) {
                var next_poll = now.add(self.delay);
                var next_from = from;

                if (messages && messages.length > 0) {
                    var last_time = _.last(messages).time;
                    next_from = last_time.add(JuttleMoment.duration(1, 'ms'));
                }

                self.program.scheduler.schedule(next_poll.unixms(), function() {
                    self.get_messages_for_timerange(next_from, to);
                });
            } else {
                self.eof();
            }
        })
        .catch(function(err) {
            self.logger.error("Could not read latest emails:", err);
        });
    },

    get_messages: function(from, to, search, pageToken) {
        var self = this;

        var opts = {
            auth: auth,
            userId: 'me',
            q: search
        };

        if (pageToken) {
            opts.pageToken = pageToken;
        }

        return self.gmail.users.messages.listAsync(opts)
        .then(function(response) {

            if (!_.has(response, "messages")) {
                return [];
            }

            self.logger.debug("Got " + response.messages.length + " potential messages");

            pageToken = response.nextPageToken;

            // Do a batch-fetch of all the message ids
            var batch = new Batchelor({
                'uri':'https://www.googleapis.com/batch',
                'method':'POST',
                'auth': {
                    'bearer': auth.credentials.access_token
                },
                'headers': {
                    'Content-Type': 'multipart/mixed'
                }
            });

            // The fields argument limits the fields returned to those
            // we are interested in.
            batch.add(_.map(response.messages, function(id) {
                return {
                    method: 'GET',
                    'path': '/gmail/v1/users/me/messages/' + id.id + "?fields=internalDate,id,snippet,payload/headers"
                };
            }));

            Promise.promisifyAll(batch);
            return batch.runAsync()
            .then(function(response) {
                return response.parts;
            });
        })
        .then(function(parts) {
            var messages = [];

            parts.forEach(function(part) {
                var message = part.body;
                var time = new JuttleMoment({rawDate: new Date(Number(message.internalDate))});

                if (time.lt(from)) {
                    return;
                }

                if (time.gt(to)) {
                    return;
                }

                var pt = {
                    time: time,
                    id: message.id,
                    snippet: message.snippet,
                    from: self.find_header(message, 'From'),
                    to: self.find_header(message, 'To'),
                    subject: self.find_header(message, 'Subject')
                };
                messages.push(pt);
            });

            if (pageToken) {
                return self.get_messages(from, to, search, pageToken).then(function(remaining_messages) {
                    return messages.concat(remaining_messages);
                });
            } else {
                return messages;
            }
        });
    },

    find_header: function(message, name) {
        var self = this;

        if (!_.has(message.payload, "headers")) {
            return "";
        }

        var match = _.find(message.payload.headers, function(header) {
            return (header.name === name);
        });

        if (match === undefined) {
            return "";
        } else {
            return match.value;
        }
    }

});

// Non-juttle pluggable adapter api functions below here.
// Taken from the quickstart page:
// https://developers.google.com/gmail/api/quickstart/nodejs, and
// then converted to use promises.

/**
 * Return an OAuth2 client with the given credentials and token.
 *
 * @param {Object} credentials The authorization client credentials.
 * @param {Object} token An OAuth2 token.
 */
function authorize(credentials, token) {
    var clientSecret = credentials.installed.client_secret;
    var clientId = credentials.installed.client_id;
    var redirectUrl = credentials.installed.redirect_uris[0];
    var auth = new googleAuth();
    var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);

    oauth2Client.credentials = token;
    return oauth2Client;
}

var GmailBackend = function(config) {

    auth = authorize(config["client-credentials"],
                     config["oauth2-token"]);

    return {
        name: 'gmail',
        read: Read
    };
};

module.exports = GmailBackend;
<commit_msg>The base class for the read proc should be source. Fix that.<commit_after>var Juttle = require('juttle/lib/runtime').Juttle;
var JuttleErrors = require('juttle/lib/errors');
var JuttleMoment = require('juttle/lib/moment/juttle-moment');
var Promise = require('bluebird');
var google = require('googleapis');
var googleAuth = require('google-auth-library');
var _ = require('underscore');
var Batchelor = require('batchelor');
var FilterGmailCompiler = require('./filter-gmail-compiler');

var auth;

var Read = Juttle.proc.source.extend({
    procName: 'read gmail',

    initialize: function(options, params, pname, location, program, juttle) {
        this.logger.debug('intitialize', options, params);
        this.gmail = google.gmail('v1');

        var time_related_options = ['from', 'to', 'last'];
        var allowed_options = time_related_options.concat(['raw']);
        var unknown = _.difference(_.keys(options), allowed_options);

        if (unknown.length > 0) {
            throw this.compile_error('RT-UNKNOWN-OPTION-ERROR',
                                     {proc: 'read gmail', option: unknown[0]});
        }

        // One of 'from', 'to', or 'last' must be present.
        var opts = _.intersection(_.keys(options), time_related_options);
        if (opts.length === 0) {
            // Waiting on a juttle release that incorporates https://github.com/juttle/juttle/pull/108
            //throw this.compile_error('RT-MISSING-TIME-RANGE-ERROR');
            throw new Error('Error: One of -from, -to, or -last must be specified to define a query time range');
        }

        // If 'from'/'to' are present, 'last' can not be present.
        if ((_.has(options, 'from') || _.has(options, 'to')) &&
            _.has(options, 'last')) {
            throw this.compile_error('RT-LAST-FROM-TO-ERROR');
        }

        // 'from' must be before 'to'
        if (_.has(options, 'from') && _.has(options, 'to') &&
            options.from > options.to) {
            throw this.compile_error('RT-TO-FROM-MOMENT-ERROR');
        }

        // If 'last' is specified, set appropriate 'from'/'to'
        if (_.has(options, 'last')) {
            this.from = program.now.subtract(options.last);
            this.to = program.now;
        } else {
            // Initialize from/to if necessary.
            this.from = options.from || program.now;
            this.to = options.to || program.now;
        }

        this.raw = options.raw;
        this.filter_search_expr = undefined;

        if (params.filter_ast) {
            this.logger.debug("Filter ast: ", params.filter_ast);
            var compiler = new FilterGmailCompiler({location: location});
            this.filter_search_expr = compiler.compile(params.filter_ast);
            this.logger.debug("Filter expression: ", this.filter_search_expr);
        }

        this.delay = options.delay || JuttleMoment.duration(1, 's');

        Promise.promisifyAll(this.gmail.users.messages);
    },

    start: function() {
        var self = this;

        return self.get_messages_for_timerange(self.from, self.to);
    },

    teardown: function() {
    },

    // Non juttle proc methods below here
    get_messages_for_timerange: function(from, to) {
        this.logger.debug('get_messages_for_timerange from=' + from + " to=" + to);
        var self = this;

        // Build a search expression given the options.
        var search = self.raw || "";

        if (this.filter_search_expr !== undefined) {
            search += " " + this.filter_search_expr;
        }

        var now = new JuttleMoment();

        // The gmail search expression only has per-day granularity,
        // so we quantize the -from and -to to the nearest day and do
        // additional filtering in get_messages().

        // XXX/mstemm not sure how the time zone is set. It's definitely not in UTC.
        search += " after:" + JuttleMoment.format(from, "YYYY/MM/DD", "US/Pacific");

        // Add 1 day to to and then quantize to a day. This
        // results in the next day, such that the before: date is
        // always in the future.
        if (! self.to.isEnd()) {
            search += " before:" + JuttleMoment.format(to.add(JuttleMoment.duration(1, "d")), "YYYY/MM/DD", "US/Pacific");
        }

        this.logger.debug("Search string:", search);
        return self.get_messages(from, to, search)
        .call("sort", function(a, b) {
            if (a.time.lt(b.time)) {
                return -1;
            } else if (a.time.eq(b.time)) {
                return 0;
            } else {
                return 1;
            }
        })
        .then(function(messages) {
            if (messages && messages.length > 0) {
                self.emit(messages);
            }
            return messages;
        })
        .then(function(messages) {
            // If to is in the future, arrange with the scheduler to
            // get all messages from the timestamp of the last message
            // to "to". Otherwise we're done.
            if (to.gt(now)) {
                var next_poll = now.add(self.delay);
                var next_from = from;

                if (messages && messages.length > 0) {
                    var last_time = _.last(messages).time;
                    next_from = last_time.add(JuttleMoment.duration(1, 'ms'));
                }

                self.program.scheduler.schedule(next_poll.unixms(), function() {
                    self.get_messages_for_timerange(next_from, to);
                });
            } else {
                self.eof();
            }
        })
        .catch(function(err) {
            self.logger.error("Could not read latest emails:", err);
        });
    },

    get_messages: function(from, to, search, pageToken) {
        var self = this;

        var opts = {
            auth: auth,
            userId: 'me',
            q: search
        };

        if (pageToken) {
            opts.pageToken = pageToken;
        }

        return self.gmail.users.messages.listAsync(opts)
        .then(function(response) {

            if (!_.has(response, "messages")) {
                return [];
            }

            self.logger.debug("Got " + response.messages.length + " potential messages");

            pageToken = response.nextPageToken;

            // Do a batch-fetch of all the message ids
            var batch = new Batchelor({
                'uri':'https://www.googleapis.com/batch',
                'method':'POST',
                'auth': {
                    'bearer': auth.credentials.access_token
                },
                'headers': {
                    'Content-Type': 'multipart/mixed'
                }
            });

            // The fields argument limits the fields returned to those
            // we are interested in.
            batch.add(_.map(response.messages, function(id) {
                return {
                    method: 'GET',
                    'path': '/gmail/v1/users/me/messages/' + id.id + "?fields=internalDate,id,snippet,payload/headers"
                };
            }));

            Promise.promisifyAll(batch);
            return batch.runAsync()
            .then(function(response) {
                return response.parts;
            });
        })
        .then(function(parts) {
            var messages = [];

            parts.forEach(function(part) {
                var message = part.body;
                var time = new JuttleMoment({rawDate: new Date(Number(message.internalDate))});

                if (time.lt(from)) {
                    return;
                }

                if (time.gt(to)) {
                    return;
                }

                var pt = {
                    time: time,
                    id: message.id,
                    snippet: message.snippet,
                    from: self.find_header(message, 'From'),
                    to: self.find_header(message, 'To'),
                    subject: self.find_header(message, 'Subject')
                };
                messages.push(pt);
            });

            if (pageToken) {
                return self.get_messages(from, to, search, pageToken).then(function(remaining_messages) {
                    return messages.concat(remaining_messages);
                });
            } else {
                return messages;
            }
        });
    },

    find_header: function(message, name) {
        var self = this;

        if (!_.has(message.payload, "headers")) {
            return "";
        }

        var match = _.find(message.payload.headers, function(header) {
            return (header.name === name);
        });

        if (match === undefined) {
            return "";
        } else {
            return match.value;
        }
    }

});

// Non-juttle pluggable adapter api functions below here.
// Taken from the quickstart page:
// https://developers.google.com/gmail/api/quickstart/nodejs, and
// then converted to use promises.

/**
 * Return an OAuth2 client with the given credentials and token.
 *
 * @param {Object} credentials The authorization client credentials.
 * @param {Object} token An OAuth2 token.
 */
function authorize(credentials, token) {
    var clientSecret = credentials.installed.client_secret;
    var clientId = credentials.installed.client_id;
    var redirectUrl = credentials.installed.redirect_uris[0];
    var auth = new googleAuth();
    var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);

    oauth2Client.credentials = token;
    return oauth2Client;
}

var GmailBackend = function(config) {

    auth = authorize(config["client-credentials"],
                     config["oauth2-token"]);

    return {
        name: 'gmail',
        read: Read
    };
};

module.exports = GmailBackend;
<|endoftext|>


<commit_before>/**
 * External dependencies.
 */

var traverse = require('estraverse').traverse;

/**
 * Internal dependencies.
 */

var Context = require('./context');
var syntax = require('./syntax');

/**
 * Analyzer.
 *
 * @constructor
 */

function Analyzer() {
  this.stack = [];

  this.contexts = [
    syntax.FunctionExpression,
    syntax.FunctionDeclaration,
    syntax.Program,
  ];

  this.score = {
    branch: 10,
    branchEmpty: 1,
    state: 3,
  };
}

/**
 * Analyze a given file.
 *
 * @param {SourceFile} source
 * @api public
 */

Analyzer.prototype.analyze = function(source) {
  this.stack = [];
  this.source = source;
  this.traverse(source.ast());
};

/**
 * Traverse.
 *
 * @param {Object} node
 * @api private
 */

Analyzer.prototype.traverse = function(ast) {
  var self = this;

  if (!ast) return;

  traverse(ast, {
    enter: function(node, parent) {
      if (self.contexts.indexOf(node.type) > -1) {
        node.context = true;
        self.pushContext(node, parent);
      }
      self.process(node);
    },
    leave: function(node) {
      if (node.context) {
        self.popContext();
      }
    }
  });
};

/**
 * Update the score.
 *
 * @param {Number} num
 * @api private
 */

Analyzer.prototype.add = function(num) {
  this.context().add(num);
};

/**
 * Add a new context into the stack.
 *
 * @param {Object} node
 * @param {Object} parent node
 * @api private
 */

Analyzer.prototype.pushContext = function(node, parentNode) {
  var parent = this.context();
  var context = new Context(node, parentNode, this.stack.length);
  if (parent) parent.children.push(context);
  this.stack.push(context);
  this.source.add(context);
};

/**
 * Remove the latest context from the stack.
 *
 * @api private
 */

Analyzer.prototype.popContext = function() {
  this.stack.pop();
};

/**
 * Return the latest context.
 *
 * @returns {Context}
 * @api private
 */

Analyzer.prototype.context = function() {
  return this.stack[this.stack.length - 1];
};

/**
 * Process `node`.
 *
 * @param {Object} node
 * @api private
 */

Analyzer.prototype.process = function(node) {
  switch (node.type) {
    case syntax.VariableDeclarator:
      return this.addDeclarator(node);
    case syntax.AssignmentExpression:
      return this.addState(node);
    case syntax.IfStatement:
      return this.addIf(node);
    case syntax.SwitchCase:
      return this.addCase(node);
    case syntax.LogicalExpression:
      return this.addLogical(node);
    case syntax.CatchClause:
    case syntax.ConditionalExpression:
    case syntax.DoWhileStatement:
    case syntax.ForStatement:
    case syntax.ForInStatement:
    case syntax.WhileStatement:
      return this.addBranch();
  }
};

/**
 * Add score for a branch.
 *
 * @api private
 */

Analyzer.prototype.addBranch = function() {
  this.add(this.score.branch);
};

/**
 * Add score for switch cases.
 *
 * @param {Object} node
 * @api private
 */

Analyzer.prototype.addCase = function(node) {
  var score = node.consequent.length
    ? this.score.branch
    : this.score.branchEmpty;

  this.add(score);
};

/**
 * Add score for if statements.
 *
 * @param {Object} node
 * @api private
 */

Analyzer.prototype.addIf = function(node) {
  if (node.alternate) this.add(this.score.branch);
  this.add(this.score.branch);
};

/**
 * Add score for assignment expressions.
 *
 * @param {Object} node
 * @api private
 */

Analyzer.prototype.addState = function(node) {
  this.add(this.score.state);
};

/**
 * Add score for variable declarators.
 *
 * @parma {Object} node
 * @api private
 */

Analyzer.prototype.addDeclarator = function(node) {
  if (node.init) this.add(this.score.state);
};

/**
 * Add score for logical expressions.
 *
 * @parma {Object} node
 * @api private
 */

Analyzer.prototype.addLogical = function(node) {
  if ('||' === node.operator) this.add(this.score.branch);
};

/**
 * Primary export.
 */

module.exports = Analyzer;
<commit_msg>Add score for function calls<commit_after>/**
 * External dependencies.
 */

var traverse = require('estraverse').traverse;

/**
 * Internal dependencies.
 */

var Context = require('./context');
var syntax = require('./syntax');

/**
 * Analyzer.
 *
 * @constructor
 */

function Analyzer() {
  this.stack = [];

  this.contexts = [
    syntax.FunctionExpression,
    syntax.FunctionDeclaration,
    syntax.Program,
  ];

  this.score = {
    branch: 10,
    branchEmpty: 1,
    state: 3,
    call: 1,
  };
}

/**
 * Analyze a given file.
 *
 * @param {SourceFile} source
 * @api public
 */

Analyzer.prototype.analyze = function(source) {
  this.stack = [];
  this.source = source;
  this.traverse(source.ast());
};

/**
 * Traverse.
 *
 * @param {Object} node
 * @api private
 */

Analyzer.prototype.traverse = function(ast) {
  var self = this;

  if (!ast) return;

  traverse(ast, {
    enter: function(node, parent) {
      if (self.contexts.indexOf(node.type) > -1) {
        node.context = true;
        self.pushContext(node, parent);
      }
      self.process(node);
    },
    leave: function(node) {
      if (node.context) {
        self.popContext();
      }
    }
  });
};

/**
 * Update the score.
 *
 * @param {Number} num
 * @api private
 */

Analyzer.prototype.add = function(num) {
  this.context().add(num);
};

/**
 * Add a new context into the stack.
 *
 * @param {Object} node
 * @param {Object} parent node
 * @api private
 */

Analyzer.prototype.pushContext = function(node, parentNode) {
  var parent = this.context();
  var context = new Context(node, parentNode, this.stack.length);
  if (parent) parent.children.push(context);
  this.stack.push(context);
  this.source.add(context);
};

/**
 * Remove the latest context from the stack.
 *
 * @api private
 */

Analyzer.prototype.popContext = function() {
  this.stack.pop();
};

/**
 * Return the latest context.
 *
 * @returns {Context}
 * @api private
 */

Analyzer.prototype.context = function() {
  return this.stack[this.stack.length - 1];
};

/**
 * Process `node`.
 *
 * @param {Object} node
 * @api private
 */

Analyzer.prototype.process = function(node) {
  switch (node.type) {
    case syntax.CallExpression:
      return this.addCall(node);
    case syntax.VariableDeclarator:
      return this.addDeclarator(node);
    case syntax.AssignmentExpression:
      return this.addState(node);
    case syntax.IfStatement:
      return this.addIf(node);
    case syntax.SwitchCase:
      return this.addCase(node);
    case syntax.LogicalExpression:
      return this.addLogical(node);
    case syntax.CatchClause:
    case syntax.ConditionalExpression:
    case syntax.DoWhileStatement:
    case syntax.ForStatement:
    case syntax.ForInStatement:
    case syntax.WhileStatement:
      return this.addBranch(node);
  }
};

/**
 * Add score for a branch.
 *
 * @api private
 */

Analyzer.prototype.addBranch = function() {
  this.add(this.score.branch);
};

/**
 * Add score for switch cases.
 *
 * @param {Object} node
 * @api private
 */

Analyzer.prototype.addCase = function(node) {
  var score = node.consequent.length
    ? this.score.branch
    : this.score.branchEmpty;

  this.add(score);
};

/**
 * Add score for if statements.
 *
 * @param {Object} node
 * @api private
 */

Analyzer.prototype.addIf = function(node) {
  if (node.alternate) this.add(this.score.branch);
  this.add(this.score.branch);
};

/**
 * Add score for assignment expressions.
 *
 * @param {Object} node
 * @api private
 */

Analyzer.prototype.addState = function(node) {
  this.add(this.score.state);
};

/**
 * Add score for variable declarators.
 *
 * @parma {Object} node
 * @api private
 */

Analyzer.prototype.addDeclarator = function(node) {
  if (node.init) this.add(this.score.state);
};

/**
 * Add score for logical expressions.
 *
 * @parma {Object} node
 * @api private
 */

Analyzer.prototype.addLogical = function(node) {
  if ('||' === node.operator) this.add(this.score.branch);
};

/**
 * Add score for function calls.
 *
 * @parma {Object} node
 * @api private
 */

Analyzer.prototype.addCall = function(node) {
  this.add(this.score.call);
};

/**
 * Primary export.
 */

module.exports = Analyzer;
<|endoftext|>


<commit_before>/*!
 * Snakeskin
 * https://github.com/SnakeskinTpl/Snakeskin
 *
 * Released under the MIT license
 * https://github.com/SnakeskinTpl/Snakeskin/blob/master/LICENSE
 */

import { $C } from '../deps/collection';
import { IS_NODE } from '../consts/hacks';
import { NULL } from '../consts/links';
import { GLOBAL_CACHE, GLOBAL_FN_CACHE } from '../consts/cache';
import { escapeEOLs } from './escape'

/**
 * Returns data from the cache by the specified cache key
 *
 * @param {?string} cacheKey - the cache key
 * @param {string} code - the source SS code
 * @param {!Object} params - runtime parameters
 * @param {!Object} ctx - an object of the source context
 * @return {(string|undefined)}
 */
export function getFromCache(cacheKey, code, params, ctx) {
	if (IS_NODE && ctx !== NULL && GLOBAL_FN_CACHE[cacheKey]) {
		$C(GLOBAL_FN_CACHE[cacheKey][code]).forEach((el, key) => {
			ctx[key] = el;
		});
	}

	const
		cache = GLOBAL_CACHE[cacheKey] && GLOBAL_CACHE[cacheKey][code];

	if (cache) {
		let
			skip = false;

		if (params.words) {
			if (!cache.words) {
				skip = true;

			} else {
				$C(cache.words).forEach((el, key) => {
					params.words[key] = el;
				});
			}
		}

		if (params.debug) {
			if (!cache.debug) {
				skip = true;

			} else {
				$C(cache.debug).forEach((el, key) => {
					params.debug[key] = el;
				});
			}
		}

		if (!skip) {
			return cache.text;
		}
	}
}

/**
 * Returns a cache key for the specified source
 *
 * @param {!Object} params - runtime parameters
 * @param {!Object} ctx - an object of the source context
 * @return {?string}
 */
export function getCacheKey(params, ctx) {
	return params.language || params.macros ?
		null : [
			params.exports,
			ctx !== NULL,
			escapeEOLs(params.eol),
			params.doctype,
			params.tolerateWhitespace,
			params.inlineIterators,
			params.renderAs,
			params.renderMode,
			params.replaceUndef,
			params.escapeOutput,
			params.prettyPrint,
			params.ignore,
			params.autoReplace,
			params.localization,
			params.i18nFn,
			params.bemFilter,
			params.useStrict
		].join();
}

/**
 * Saves compiled template functions in the cache
 *
 * @param {?string} cacheKey - the cache key
 * @param {string} code - the source SS code
 * @param {!Object} params - runtime parameters
 * @param {!Object} ctx - an object of the source context
 */
export function saveIntoFnCache(cacheKey, code, params, ctx) {
	if (ctx !== NULL) {
		ctx['init'](Snakeskin);

		if (cacheKey && (params.cache || GLOBAL_FN_CACHE[cacheKey])) {
			GLOBAL_FN_CACHE[cacheKey] = GLOBAL_FN_CACHE[cacheKey] || {};
			GLOBAL_FN_CACHE[cacheKey][code] = ctx;
		}
	}
}

/**
 * Saves templates in the cache
 *
 * @param {?string} cacheKey - the cache key
 * @param {string} code - the source SS code
 * @param {!Object} params - runtime parameters
 * @param {!Parser} parser - an instance of Parser class
 */
export function saveIntoCache(cacheKey, code, params, parser) {
	if (cacheKey && (params.cache || GLOBAL_CACHE[cacheKey])) {
		GLOBAL_CACHE[cacheKey] = GLOBAL_CACHE[cacheKey] || {};
		GLOBAL_CACHE[cacheKey][code] = {
			text: parser.res,
			words: params.words,
			debug: params.debug
		};
	}
}
<commit_msg>Refactoring comments<commit_after>/*!
 * Snakeskin
 * https://github.com/SnakeskinTpl/Snakeskin
 *
 * Released under the MIT license
 * https://github.com/SnakeskinTpl/Snakeskin/blob/master/LICENSE
 */

import { $C } from '../deps/collection';
import { IS_NODE } from '../consts/hacks';
import { NULL } from '../consts/links';
import { GLOBAL_CACHE, GLOBAL_FN_CACHE } from '../consts/cache';
import { escapeEOLs } from './escape'

/**
 * Returns data from the cache by a cache key
 *
 * @param {?string} cacheKey - the cache key
 * @param {string} code - the source SS code
 * @param {!Object} params - runtime parameters
 * @param {!Object} ctx - an object of the source context
 * @return {(string|undefined)}
 */
export function getFromCache(cacheKey, code, params, ctx) {
	if (IS_NODE && ctx !== NULL && GLOBAL_FN_CACHE[cacheKey]) {
		$C(GLOBAL_FN_CACHE[cacheKey][code]).forEach((el, key) => {
			ctx[key] = el;
		});
	}

	const
		cache = GLOBAL_CACHE[cacheKey] && GLOBAL_CACHE[cacheKey][code];

	if (cache) {
		let
			skip = false;

		if (params.words) {
			if (!cache.words) {
				skip = true;

			} else {
				$C(cache.words).forEach((el, key) => {
					params.words[key] = el;
				});
			}
		}

		if (params.debug) {
			if (!cache.debug) {
				skip = true;

			} else {
				$C(cache.debug).forEach((el, key) => {
					params.debug[key] = el;
				});
			}
		}

		if (!skip) {
			return cache.text;
		}
	}
}

/**
 * Returns a cache key for a source
 *
 * @param {!Object} params - runtime parameters
 * @param {!Object} ctx - an object of the source context
 * @return {?string}
 */
export function getCacheKey(params, ctx) {
	return params.language || params.macros ?
		null : [
			params.exports,
			ctx !== NULL,
			escapeEOLs(params.eol),
			params.doctype,
			params.tolerateWhitespace,
			params.inlineIterators,
			params.renderAs,
			params.renderMode,
			params.replaceUndef,
			params.escapeOutput,
			params.prettyPrint,
			params.ignore,
			params.autoReplace,
			params.localization,
			params.i18nFn,
			params.bemFilter,
			params.useStrict
		].join();
}

/**
 * Saves compiled template functions in the cache by a cache key
 *
 * @param {?string} cacheKey - the cache key
 * @param {string} code - the source SS code
 * @param {!Object} params - runtime parameters
 * @param {!Object} ctx - an object of the source context
 */
export function saveIntoFnCache(cacheKey, code, params, ctx) {
	if (ctx !== NULL) {
		ctx['init'](Snakeskin);

		if (cacheKey && (params.cache || GLOBAL_FN_CACHE[cacheKey])) {
			GLOBAL_FN_CACHE[cacheKey] = GLOBAL_FN_CACHE[cacheKey] || {};
			GLOBAL_FN_CACHE[cacheKey][code] = ctx;
		}
	}
}

/**
 * Saves templates in the cache by a cache key
 *
 * @param {?string} cacheKey - the cache key
 * @param {string} code - the source SS code
 * @param {!Object} params - runtime parameters
 * @param {!Parser} parser - an instance of Parser class
 */
export function saveIntoCache(cacheKey, code, params, parser) {
	if (cacheKey && (params.cache || GLOBAL_CACHE[cacheKey])) {
		GLOBAL_CACHE[cacheKey] = GLOBAL_CACHE[cacheKey] || {};
		GLOBAL_CACHE[cacheKey][code] = {
			text: parser.res,
			words: params.words,
			debug: params.debug
		};
	}
}
<|endoftext|>


<commit_before>//default service locations
var proxyLocation = process.env.X509_USER_PROXY ? process.env.X509_USER_PROXY : 'user-proxy.pem';
var atmoLocation = process.env.ATMOSPHERE_URL ? process.env.ATMOSPHERE_URL : 'cloud.plgrid.pl';

//default ids of vms
var wfMainId = process.env.WF_MAIN_ID ? process.env.WF_MAIN_ID : 19;
var wfWorkerId = process.env.WF_WORDKER_ID ? process.env.WF_WORDKER_ID : 25;

//default location of user stored config for hflowc
var configLocations = [
    '~/.hflow.config.json'
];

module.exports.default_config = {
    proxyLocation: proxyLocation,
    atmoLocation: atmoLocation,
    wfMainId: wfMainId,
    wfWorkerId: wfWorkerId
};

module.exports.configLocations = configLocations;<commit_msg>Update template ids in default config.<commit_after>//default service locations
var proxyLocation = process.env.X509_USER_PROXY ? process.env.X509_USER_PROXY : 'user-proxy.pem';
var atmoLocation = process.env.ATMOSPHERE_URL ? process.env.ATMOSPHERE_URL : 'cloud.plgrid.pl';

//default ids of vms
var wfMainId = process.env.WF_MAIN_ID ? process.env.WF_MAIN_ID : 28;
var wfWorkerId = process.env.WF_WORDKER_ID ? process.env.WF_WORDKER_ID : 29;

//default location of user stored config for hflowc
var configLocations = [
    '~/.hflow.config.json'
];

module.exports.default_config = {
    proxyLocation: proxyLocation,
    atmoLocation: atmoLocation,
    wfMainId: wfMainId,
    wfWorkerId: wfWorkerId
};

module.exports.configLocations = configLocations;<|endoftext|>


<commit_before>/*!
 * iamport
 * Copyright(c) 2016 Seungjae Lee (a0ly)
 * MIT Licensed
 */

'use strict';

var querystring = require('querystring');
var Promise = require('bluebird');
var _ = require('lodash');
var hasOwn = {}.hasOwnProperty;

/** method 생성 함수
 * API path 생성
 * URL Param, Form Param require check
 *
 * @returns resource method [function]
 * @public
*/
function iamportMethod (spec) {
  var requestMethod = (spec.method || 'GET').toUpperCase();
  return function(param) {
    param = param || {};

    var _this = this,
        apiParams = [this._host, this.path],
        API_BASE, formData;

    if(spec.command) {
      apiParams.push(spec.command);
    }

    if(spec.urlParam) {
      if( hasOwn.call(param, spec.urlParam) ) {
        apiParams.push( param[spec.urlParam] );
        param = _.omit(param, spec.urlParam);
      } else {
        return new Promise(function(resolve, reject) {
          reject(new Error('Param missing: ' + spec.urlParam));
        });
      }
    }

    if(spec.require && spec.require.length) {
      for(var i=0; i<spec.require.length; i++) {
        if (!hasOwn.call( param, spec.require[i] )) {
          return new Promise(function(resolve, reject) {
            reject(new Error('Param missing: ' + spec.require[i]));
          });
        }
      }
    }

    API_BASE = apiParams.join('/');

    if( requestMethod === 'POST' ) formData = param;
    else if (Object.keys(param).length > 0) API_BASE += '?' + querystring.stringify(param);

    return _this._makeRequest(requestMethod, API_BASE, formData);
  };
}

/**
 * Module exports.
 * @public
 */
module.exports = iamportMethod;
<commit_msg>in ccoropraion payment, pwd_2digit is not required<commit_after>/*!
 * iamport
 * Copyright(c) 2016 Seungjae Lee (a0ly)
 * MIT Licensed
 */

'use strict';

var querystring = require('querystring');
var Promise = require('bluebird');
var _ = require('lodash');
var hasOwn = {}.hasOwnProperty;

/** method 생성 함수
 * API path 생성
 * URL Param, Form Param require check
 *
 * @returns resource method [function]
 * @public
*/
function iamportMethod (spec) {
  var requestMethod = (spec.method || 'GET').toUpperCase();
  return function(param) {
    param = param || {};

    var _this = this,
        apiParams = [this._host, this.path],
        API_BASE, formData;

    if(spec.command) {
      apiParams.push(spec.command);
    }

    if(spec.urlParam) {
      if( hasOwn.call(param, spec.urlParam) ) {
        apiParams.push( param[spec.urlParam] );
        param = _.omit(param, spec.urlParam);
      } else {
        return new Promise(function(resolve, reject) {
          reject(new Error('Param missing: ' + spec.urlParam));
        });
      }
    }

    if(spec.require && spec.require.length) {
      for(var i=0; i<spec.require.length; i++) {
        if (!hasOwn.call( param, spec.require[i] )) {
          if(spec.require[i] === 'pwd_2digit'
            && param['birth']
            && param['birth'].length > 6){
            continue;
          }

          return new Promise(function(resolve, reject) {
            reject(new Error('Param missing: ' + spec.require[i]));
          });
        }
      }
    }

    API_BASE = apiParams.join('/');

    if( requestMethod === 'POST' ) formData = param;
    else if (Object.keys(param).length > 0) API_BASE += '?' + querystring.stringify(param);

    return _this._makeRequest(requestMethod, API_BASE, formData);
  };
}

/**
 * Module exports.
 * @public
 */
module.exports = iamportMethod;
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * Portions Copyright (C) Philipp Kewisch, 2011-2012 */

"use strict";

(typeof(ICAL) === 'undefined')? ICAL = {} : '';
(function() {
  var OPTIONS = ["tzid", "location", "tznames",
                 "latitude", "longitude"];

  /**
   * Timezone representation, created by passing in a tzid and component.
   *
   *    var vcalendar;
   *    var timezoneComp = vcalendar.getFirstSubcomponent('vtimezone');
   *    var tzid = timezoneComp.getFirstPropertyValue('tzid');
   *
   *    var timezone = new ICAL.Timezone({
   *      component: timezoneComp,
   *      tzid
   *    });
   *
   *
   * @param {Object} data options for class (see above).
   */
  ICAL.Timezone = function icaltimezone(data) {
    this.wrappedJSObject = this;
    this.fromData(data);
  };

  ICAL.Timezone.prototype = {

    tzid: "",
    location: "",
    tznames: "",

    latitude: 0.0,
    longitude: 0.0,

    component: null,

    expand_end_year: 0,
    expand_start_year: 0,

    changes: null,
    icalclass: "icaltimezone",

    fromData: function fromData(aData) {
      if (aData instanceof ICAL.Component) {
        this.component = aData;
        this.tzid = this.component.getFirstPropertyValue('tzid');
        return;
      }

      for (var key in OPTIONS) {
        var prop = OPTIONS[key];
        if (aData && prop in aData) {
          this[prop] = aData[prop];
        }
      }

      this.expand_end_year = 0;
      this.expand_start_year = 0;
      if (aData && "component" in aData) {
        if (typeof aData.component == "string") {
          this.component = this.componentFromString(aData.component);
        } else {
          this.component = aData.component;
        }
      } else {
        this.component = null;
      }
      return this;
    },

    /**
     * Finds the utcOffset the given time would occur in this timezone.
     *
     * @return {Number} utc offset in seconds.
     */
    utcOffset: function utcOffset(tt) {
      if (this == ICAL.Timezone.utcTimezone || this == ICAL.Timezone.localTimezone) {
        return 0;
      }

      this._ensureCoverage(tt.year);

      if (!this.changes || this.changes.length == 0) {
        return 0;
      }

      var tt_change = {
        year: tt.year,
        month: tt.month,
        day: tt.day,
        hour: tt.hour,
        minute: tt.minute,
        second: tt.second
      };

      var change_num = this._findNearbyChange(tt_change);
      var change_num_to_use = -1;
      var step = 1;

      // TODO: replace with bin search?
      for (;;) {
        var change = ICAL.helpers.clone(this.changes[change_num], true);
        if (change.utcOffset < change.prev_utcOffset) {
          ICAL.helpers.dumpn("Adjusting " + change.utcOffset);
          ICAL.Timezone.adjust_change(change, 0, 0, 0, change.utcOffset);
        } else {
          ICAL.helpers.dumpn("Adjusting prev " + change.prev_utcOffset);
          ICAL.Timezone.adjust_change(change, 0, 0, 0,
                                          change.prev_utcOffset);
        }

        var cmp = ICAL.Timezone._compare_change_fn(tt_change, change);
        ICAL.helpers.dumpn("Compare" + cmp + " / " + change.toString());

        if (cmp >= 0) {
          change_num_to_use = change_num;
        } else {
          step = -1;
        }

        if (step == -1 && change_num_to_use != -1) {
          break;
        }

        change_num += step;

        if (change_num < 0) {
          return 0;
        }

        if (change_num >= this.changes.length) {
          break;
        }
      }

      var zone_change = this.changes[change_num_to_use];
      var utcOffset_change = zone_change.utcOffset - zone_change.prev_utcOffset;

      if (utcOffset_change < 0 && change_num_to_use > 0) {
        var tmp_change = ICAL.helpers.clone(zone_change, true);
        ICAL.Timezone.adjust_change(tmp_change, 0, 0, 0,
                                        tmp_change.prev_utcOffset);

        if (ICAL.Timezone._compare_change_fn(tt_change, tmp_change) < 0) {
          var prev_zone_change = this.changes[change_num_to_use - 1];

          var want_daylight = false; // TODO

          if (zone_change.is_daylight != want_daylight &&
              prev_zone_change.is_daylight == want_daylight) {
            zone_change = prev_zone_change;
          }
        }
      }

      // TODO return is_daylight?
      return zone_change.utcOffset;
    },

    _findNearbyChange: function icaltimezone_find_nearby_change(change) {
      var lower = 0,
        middle = 0;
      var upper = this.changes.length;

      while (lower < upper) {
        middle = ICAL.helpers.trunc(lower + upper / 2);
        var zone_change = this.changes[middle];
        var cmp = ICAL.Timezone._compare_change_fn(change, zone_change);
        if (cmp == 0) {
          break;
        } else if (cmp > 0) {
          upper = middle;
        } else {
          lower = middle;
        }
      }

      return middle;
    },

    _ensureCoverage: function(aYear) {
      if (ICAL.Timezone._minimum_expansion_year == -1) {
        var today = ICAL.Time.now();
        ICAL.Timezone._minimum_expansion_year = today.year;
      }

      var changes_end_year = aYear;
      if (changes_end_year < ICAL.Timezone._minimum_expansion_year) {
        changes_end_year = ICAL.Timezone._minimum_expansion_year;
      }

      changes_end_year += ICAL.Timezone.EXTRA_COVERAGE;

      if (changes_end_year > ICAL.Timezone.MAX_YEAR) {
        changes_end_year = ICAL.Timezone.MAX_YEAR;
      }

      if (!this.changes || this.expand_end_year < aYear) {
        this._expandChanges(changes_end_year);
      }
    },

    _expandChanges: function(aYear) {
      var changes = [];
      if (this.component) {
        // HACK checking for component only needed for floating
        // tz, which is not in core libical.
        var subcomps = this.component.getAllSubcomponents();
        for (var compkey in subcomps) {
          this._expandComponent(subcomps[compkey], aYear, changes);
        }

        this.changes = changes.concat(this.changes || []);
        this.changes.sort(ICAL.Timezone._compare_change_fn);
      }

      this.change_end_year = aYear;
    },

    _expandComponent: function(aComponent, aYear, changes) {
      if (!aComponent.hasProperty("dtstart") ||
          !aComponent.hasProperty("tzoffsetto") ||
          !aComponent.hasProperty("tzoffsetfrom")) {
        return null;
      }

      var dtstart = aComponent.getFirstProperty("dtstart").getFirstValue();

      function convert_tzoffset(offset) {
        return offset.factor * (offset.hours * 3600 + offset.minutes * 60);
      }

      function init_changes() {
        var changebase = {};
        changebase.is_daylight = (aComponent.name == "daylight");
        changebase.utcOffset = convert_tzoffset(
          aComponent.getFirstProperty("tzoffsetto").getFirstValue()
        );

        changebase.prev_utcOffset = convert_tzoffset(
          aComponent.getFirstProperty("tzoffsetfrom").getFirstValue()
        );

        return changebase;
      }

      if (!aComponent.hasProperty("rrule") && !aComponent.hasProperty("rdate")) {
        var change = init_changes();
        change.year = dtstart.year;
        change.month = dtstart.month;
        change.day = dtstart.day;
        change.hour = dtstart.hour;
        change.minute = dtstart.minute;
        change.second = dtstart.second;

        ICAL.Timezone.adjust_change(change, 0, 0, 0,
                                        -change.prev_utcOffset);
        changes.push(change);
      } else {
        var props = aComponent.getAllProperties("rdate");
        for (var rdatekey in props) {
          var rdate = props[rdatekey];
          var change = init_changes();
          change.year = rdate.time.year;
          change.month = rdate.time.month;
          change.day = rdate.time.day;

          if (rdate.time.isDate) {
            change.hour = dtstart.hour;
            change.minute = dtstart.minute;
            change.second = dtstart.second;
          } else {
            change.hour = rdate.time.hour;
            change.minute = rdate.time.minute;
            change.second = rdate.time.second;

            if (rdate.time.zone == ICAL.Timezone.utcTimezone) {
              ICAL.Timezone.adjust_change(change, 0, 0, 0,
                                              -change.prev_utcOffset);
            }
          }

          changes.push(change);
        }

        var rrule = aComponent.getFirstProperty("rrule").getFirstValue();
        // TODO multiple rrules?

        var change = init_changes();

        if (rrule.until && rrule.until.zone == ICAL.Timezone.utcTimezone) {
          rrule.until.adjust(0, 0, 0, change.prev_utcOffset);
          rrule.until.zone = ICAL.Timezone.localTimezone;
        }

        var iterator = rrule.iterator(dtstart);

        var occ;
        while ((occ = iterator.next())) {
          var change = init_changes();
          if (occ.year > aYear || !occ) {
            break;
          }

          change.year = occ.year;
          change.month = occ.month;
          change.day = occ.day;
          change.hour = occ.hour;
          change.minute = occ.minute;
          change.second = occ.second;
          change.isDate = occ.isDate;

          ICAL.Timezone.adjust_change(change, 0, 0, 0,
                                          -change.prev_utcOffset);
          changes.push(change);
        }
      }

      return changes;
    },

    toString: function toString() {
      return (this.tznames ? this.tznames : this.tzid);
    }

  };

  ICAL.Timezone._compare_change_fn = function icaltimezone_compare_change_fn(a, b) {
    if (a.year < b.year) return -1;
    else if (a.year > b.year) return 1;

    if (a.month < b.month) return -1;
    else if (a.month > b.month) return 1;

    if (a.day < b.day) return -1;
    else if (a.day > b.day) return 1;

    if (a.hour < b.hour) return -1;
    else if (a.hour > b.hour) return 1;

    if (a.minute < b.minute) return -1;
    else if (a.minute > b.minute) return 1;

    if (a.second < b.second) return -1;
    else if (a.second > b.second) return 1;

    return 0;
  };

  ICAL.Timezone.convert_time = function icaltimezone_convert_time(tt, from_zone, to_zone) {
    if (tt.isDate ||
        from_zone.tzid == to_zone.tzid ||
        from_zone == ICAL.Timezone.localTimezone ||
        to_zone == ICAL.Timezone.localTimezone) {
      tt.zone = to_zone;
      return tt;
    }

    var utcOffset = from_zone.utcOffset(tt);
    tt.adjust(0, 0, 0, - utcOffset);

    utcOffset = to_zone.utcOffset(tt);
    tt.adjust(0, 0, 0, utcOffset);

    return null;
  };

  ICAL.Timezone.fromData = function icaltimezone_fromData(aData) {
    var tt = new ICAL.Timezone();
    return tt.fromData(aData);
  };

  ICAL.Timezone.utcTimezone = ICAL.Timezone.fromData({
    tzid: "UTC"
  });

  ICAL.Timezone.localTimezone = ICAL.Timezone.fromData({
    tzid: "floating"
  });

  ICAL.Timezone.adjust_change = function icaltimezone_adjust_change(change, days, hours, minutes, seconds) {
    return ICAL.Time.prototype.adjust.call(
      change,
      days,
      hours,
      minutes,
      seconds,
      change
    );
  };

  ICAL.Timezone._minimum_expansion_year = -1;
  ICAL.Timezone.MAX_YEAR = 2035; // TODO this is because of time_t, which we don't need. Still usefull?
  ICAL.Timezone.EXTRA_COVERAGE = 5;
})();
<commit_msg>fixed bug in faulty binsearch<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * Portions Copyright (C) Philipp Kewisch, 2011-2012 */

"use strict";

(typeof(ICAL) === 'undefined')? ICAL = {} : '';
(function() {
  var OPTIONS = ["tzid", "location", "tznames",
                 "latitude", "longitude"];

  /**
   * Timezone representation, created by passing in a tzid and component.
   *
   *    var vcalendar;
   *    var timezoneComp = vcalendar.getFirstSubcomponent('vtimezone');
   *    var tzid = timezoneComp.getFirstPropertyValue('tzid');
   *
   *    var timezone = new ICAL.Timezone({
   *      component: timezoneComp,
   *      tzid
   *    });
   *
   *
   * @param {Object} data options for class (see above).
   */
  ICAL.Timezone = function icaltimezone(data) {
    this.wrappedJSObject = this;
    this.fromData(data);
  };

  ICAL.Timezone.prototype = {

    tzid: "",
    location: "",
    tznames: "",

    latitude: 0.0,
    longitude: 0.0,

    component: null,

    expand_end_year: 0,
    expand_start_year: 0,

    changes: null,
    icalclass: "icaltimezone",

    fromData: function fromData(aData) {
      if (aData instanceof ICAL.Component) {
        this.component = aData;
        this.tzid = this.component.getFirstPropertyValue('tzid');
        return;
      }

      for (var key in OPTIONS) {
        var prop = OPTIONS[key];
        if (aData && prop in aData) {
          this[prop] = aData[prop];
        }
      }

      this.expand_end_year = 0;
      this.expand_start_year = 0;
      if (aData && "component" in aData) {
        if (typeof aData.component == "string") {
          this.component = this.componentFromString(aData.component);
        } else {
          this.component = aData.component;
        }
      } else {
        this.component = null;
      }
      return this;
    },

    /**
     * Finds the utcOffset the given time would occur in this timezone.
     *
     * @return {Number} utc offset in seconds.
     */
    utcOffset: function utcOffset(tt) {
      if (this == ICAL.Timezone.utcTimezone || this == ICAL.Timezone.localTimezone) {
        return 0;
      }

      this._ensureCoverage(tt.year);

      if (!this.changes || this.changes.length == 0) {
        return 0;
      }

      var tt_change = {
        year: tt.year,
        month: tt.month,
        day: tt.day,
        hour: tt.hour,
        minute: tt.minute,
        second: tt.second
      };

      var change_num = this._findNearbyChange(tt_change);
      var change_num_to_use = -1;
      var step = 1;

      // TODO: replace with bin search?
      for (;;) {
        //console.log(change_num, change_num_to_use);
        var change = ICAL.helpers.clone(this.changes[change_num], true);
        if (change.utcOffset < change.prev_utcOffset) {
          //console.log("Adjusting " + change.utcOffset);
          ICAL.Timezone.adjust_change(change, 0, 0, 0, change.utcOffset);
        } else {
          //console.log("Adjusting prev " + change.prev_utcOffset);
          ICAL.Timezone.adjust_change(change, 0, 0, 0,
                                          change.prev_utcOffset);
        }

        var cmp = ICAL.Timezone._compare_change_fn(tt_change, change);
        ICAL.helpers.dumpn("Compare" + cmp + " / " + change.toString());

        if (cmp >= 0) {
          change_num_to_use = change_num;
        } else {
          step = -1;
        }

        if (step == -1 && change_num_to_use != -1) {
          break;
        }

        change_num += step;

        if (change_num < 0) {
          return 0;
        }

        if (change_num >= this.changes.length) {
          break;
        }
      }

      var zone_change = this.changes[change_num_to_use];
      var utcOffset_change = zone_change.utcOffset - zone_change.prev_utcOffset;

      if (utcOffset_change < 0 && change_num_to_use > 0) {
        var tmp_change = ICAL.helpers.clone(zone_change, true);
        ICAL.Timezone.adjust_change(tmp_change, 0, 0, 0,
                                        tmp_change.prev_utcOffset);

        if (ICAL.Timezone._compare_change_fn(tt_change, tmp_change) < 0) {
          var prev_zone_change = this.changes[change_num_to_use - 1];

          var want_daylight = false; // TODO

          if (zone_change.is_daylight != want_daylight &&
              prev_zone_change.is_daylight == want_daylight) {
            zone_change = prev_zone_change;
          }
        }
      }

      // TODO return is_daylight?
      return zone_change.utcOffset;
    },

    _findNearbyChange: function icaltimezone_find_nearby_change(change) {
      // find the closest match
      var idx = ICAL.helpers.binsearchInsert(
        this.changes,
        change,
        ICAL.Timezone._compare_change_fn
      );

      if (idx >= this.changes.length) {
        return this.changes.length - 1;
      }

      return idx;
    },

    _ensureCoverage: function(aYear) {
      if (ICAL.Timezone._minimum_expansion_year == -1) {
        var today = ICAL.Time.now();
        ICAL.Timezone._minimum_expansion_year = today.year;
      }

      var changes_end_year = aYear;
      if (changes_end_year < ICAL.Timezone._minimum_expansion_year) {
        changes_end_year = ICAL.Timezone._minimum_expansion_year;
      }

      changes_end_year += ICAL.Timezone.EXTRA_COVERAGE;

      if (changes_end_year > ICAL.Timezone.MAX_YEAR) {
        changes_end_year = ICAL.Timezone.MAX_YEAR;
      }

      if (!this.changes || this.expand_end_year < aYear) {
        this._expandChanges(changes_end_year);
      }
    },

    _expandChanges: function(aYear) {
      var changes = [];
      if (this.component) {
        // HACK checking for component only needed for floating
        // tz, which is not in core libical.
        var subcomps = this.component.getAllSubcomponents();
        for (var compkey in subcomps) {
          this._expandComponent(subcomps[compkey], aYear, changes);
        }

        this.changes = changes.concat(this.changes || []);
        this.changes.sort(ICAL.Timezone._compare_change_fn);
      }

      this.change_end_year = aYear;
    },

    _expandComponent: function(aComponent, aYear, changes) {
      if (!aComponent.hasProperty("dtstart") ||
          !aComponent.hasProperty("tzoffsetto") ||
          !aComponent.hasProperty("tzoffsetfrom")) {
        return null;
      }

      var dtstart = aComponent.getFirstProperty("dtstart").getFirstValue();

      function convert_tzoffset(offset) {
        return offset.factor * (offset.hours * 3600 + offset.minutes * 60);
      }

      function init_changes() {
        var changebase = {};
        changebase.is_daylight = (aComponent.name == "daylight");
        changebase.utcOffset = convert_tzoffset(
          aComponent.getFirstProperty("tzoffsetto").getFirstValue()
        );

        changebase.prev_utcOffset = convert_tzoffset(
          aComponent.getFirstProperty("tzoffsetfrom").getFirstValue()
        );

        return changebase;
      }

      if (!aComponent.hasProperty("rrule") && !aComponent.hasProperty("rdate")) {
        var change = init_changes();
        change.year = dtstart.year;
        change.month = dtstart.month;
        change.day = dtstart.day;
        change.hour = dtstart.hour;
        change.minute = dtstart.minute;
        change.second = dtstart.second;

        ICAL.Timezone.adjust_change(change, 0, 0, 0,
                                        -change.prev_utcOffset);
        changes.push(change);
      } else {
        var props = aComponent.getAllProperties("rdate");
        for (var rdatekey in props) {
          var rdate = props[rdatekey];
          var change = init_changes();
          change.year = rdate.time.year;
          change.month = rdate.time.month;
          change.day = rdate.time.day;

          if (rdate.time.isDate) {
            change.hour = dtstart.hour;
            change.minute = dtstart.minute;
            change.second = dtstart.second;
          } else {
            change.hour = rdate.time.hour;
            change.minute = rdate.time.minute;
            change.second = rdate.time.second;

            if (rdate.time.zone == ICAL.Timezone.utcTimezone) {
              ICAL.Timezone.adjust_change(change, 0, 0, 0,
                                              -change.prev_utcOffset);
            }
          }

          changes.push(change);
        }

        var rrule = aComponent.getFirstProperty("rrule").getFirstValue();
        // TODO multiple rrules?

        var change = init_changes();

        if (rrule.until && rrule.until.zone == ICAL.Timezone.utcTimezone) {
          rrule.until.adjust(0, 0, 0, change.prev_utcOffset);
          rrule.until.zone = ICAL.Timezone.localTimezone;
        }

        var iterator = rrule.iterator(dtstart);

        var occ;
        while ((occ = iterator.next())) {
          var change = init_changes();
          if (occ.year > aYear || !occ) {
            break;
          }

          change.year = occ.year;
          change.month = occ.month;
          change.day = occ.day;
          change.hour = occ.hour;
          change.minute = occ.minute;
          change.second = occ.second;
          change.isDate = occ.isDate;

          ICAL.Timezone.adjust_change(change, 0, 0, 0,
                                          -change.prev_utcOffset);
          changes.push(change);
        }
      }

      return changes;
    },

    toString: function toString() {
      return (this.tznames ? this.tznames : this.tzid);
    }

  };

  ICAL.Timezone._compare_change_fn = function icaltimezone_compare_change_fn(a, b) {
    if (a.year < b.year) return -1;
    else if (a.year > b.year) return 1;

    if (a.month < b.month) return -1;
    else if (a.month > b.month) return 1;

    if (a.day < b.day) return -1;
    else if (a.day > b.day) return 1;

    if (a.hour < b.hour) return -1;
    else if (a.hour > b.hour) return 1;

    if (a.minute < b.minute) return -1;
    else if (a.minute > b.minute) return 1;

    if (a.second < b.second) return -1;
    else if (a.second > b.second) return 1;

    return 0;
  };

  ICAL.Timezone.convert_time = function icaltimezone_convert_time(tt, from_zone, to_zone) {
    if (tt.isDate ||
        from_zone.tzid == to_zone.tzid ||
        from_zone == ICAL.Timezone.localTimezone ||
        to_zone == ICAL.Timezone.localTimezone) {
      tt.zone = to_zone;
      return tt;
    }

    var utcOffset = from_zone.utcOffset(tt);
    tt.adjust(0, 0, 0, - utcOffset);

    utcOffset = to_zone.utcOffset(tt);
    tt.adjust(0, 0, 0, utcOffset);

    return null;
  };

  ICAL.Timezone.fromData = function icaltimezone_fromData(aData) {
    var tt = new ICAL.Timezone();
    return tt.fromData(aData);
  };

  ICAL.Timezone.utcTimezone = ICAL.Timezone.fromData({
    tzid: "UTC"
  });

  ICAL.Timezone.localTimezone = ICAL.Timezone.fromData({
    tzid: "floating"
  });

  ICAL.Timezone.adjust_change = function icaltimezone_adjust_change(change, days, hours, minutes, seconds) {
    return ICAL.Time.prototype.adjust.call(
      change,
      days,
      hours,
      minutes,
      seconds,
      change
    );
  };

  ICAL.Timezone._minimum_expansion_year = -1;
  ICAL.Timezone.MAX_YEAR = 2035; // TODO this is because of time_t, which we don't need. Still usefull?
  ICAL.Timezone.EXTRA_COVERAGE = 5;
})();
<|endoftext|>


<commit_before>/*
 * Copyright 2014-2015 Fabian Tollenaar <fabian@starting-point.nl>
 *
 * 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
 *
 *     http://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.
 */
var flatMap = require('flatmap');

module.exports = function(app) {
  'use strict';

  var _ = require('lodash'),
    debug = require('debug')('signalk-server:interfaces:ws'),
    Primus = require('primus'),
    api = {},
    started = false,
    primus;

  api.mdns = {
    name: "_signalk-ws",
    type: "tcp",
    port: app.config.port
  };

  api.start = function() {
    debug('Starting Primus/WS interface');

    started = true;

    primus = new Primus(app.server, {
      transformer: 'websockets',
      pathname: '/signalk/v1/stream',
      timeout: false
    });

    primus.on('connection', function(spark) {
      debug(spark.id + " connected with params " + JSON.stringify(spark.query));

      if (spark.query.stream === 'subscribe') {
        debug('stream:subscribe');
        //inefficient for many sparks, should instead keep track of which sparks subscribe to which paths
        spark.paths = {};
        event = 'change:delta';
        onChange = function(delta) {
          console.log(delta)
          normalizeDelta(delta).forEach(function(oneValueDelta) {
            var doSend = oneValueDelta.updates.some(function(update) {
              return update.values.some(function(value) {
                return spark.paths[value.path];
              });
            });
            if (doSend) {
              spark.write(oneValueDelta);
            }
          });
        };
        spark.on('data', function(msg) {
          debug("<" + JSON.stringify(msg));
          if (msg.command === 'subscribe') {
            msg.paths.forEach(function(path) {
              spark.paths[path] = true;
            });
          }
          debug(spark.paths);
        });
      }

      app.signalk.on('change:delta', function(data) {
        spark.write(data)
      });

      spark.onDisconnect = function() {
        app.signalk.removeListener('change:delta', onChange);
      }

      spark.write({
        name: app.config.name,
        version: app.config.version,
        timestamp: new Date(),
        self: app.selfId,
        roles: ["master", "main"]
      })

    });

    primus.on('disconnection', function(spark) {
      spark.onDisconnect();
      debug(spark.id + " disconnected");
    });
  };

  api.stop = function() {
    if (primus.destroy && started) {
      debug("Destroying primus...");
      primus.destroy({
        close: false,
        timeout: 500
      });
    }
  };

  return api;
};

function normalizeDelta(delta) {
  return flatMap(delta.updates, normalizeUpdate).map(function(update) {
    return {
      context: delta.context,
      updates: [update]
    }
  })
};

function normalizeUpdate(update) {
  return update.values.map(function(value) {
    return {
      source: update.source,
      values: [value]
    }
  })
}
<commit_msg>Let's not leak listeners<commit_after>/*
 * Copyright 2014-2015 Fabian Tollenaar <fabian@starting-point.nl>
 *
 * 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
 *
 *     http://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.
 */
var flatMap = require('flatmap');

module.exports = function(app) {
  'use strict';

  var _ = require('lodash'),
    debug = require('debug')('signalk-server:interfaces:ws'),
    Primus = require('primus'),
    api = {},
    started = false,
    primus;

  api.mdns = {
    name: "_signalk-ws",
    type: "tcp",
    port: app.config.port
  };

  api.start = function() {
    debug('Starting Primus/WS interface');

    started = true;

    primus = new Primus(app.server, {
      transformer: 'websockets',
      pathname: '/signalk/v1/stream',
      timeout: false
    });

    primus.on('connection', function(spark) {
      debug(spark.id + " connected with params " + JSON.stringify(spark.query));

      if (spark.query.stream === 'subscribe') {
        debug('stream:subscribe');
        //inefficient for many sparks, should instead keep track of which sparks subscribe to which paths
        spark.paths = {};
        event = 'change:delta';
        onChange = function(delta) {
          console.log(delta)
          normalizeDelta(delta).forEach(function(oneValueDelta) {
            var doSend = oneValueDelta.updates.some(function(update) {
              return update.values.some(function(value) {
                return spark.paths[value.path];
              });
            });
            if (doSend) {
              spark.write(oneValueDelta);
            }
          });
        };
        spark.on('data', function(msg) {
          debug("<" + JSON.stringify(msg));
          if (msg.command === 'subscribe') {
            msg.paths.forEach(function(path) {
              spark.paths[path] = true;
            });
          }
          debug(spark.paths);
        });
      }

      var onChange = function(data) {
        spark.write(data);
      }

      app.signalk.on('change:delta', onChange);

      spark.onDisconnect = function() {
        app.signalk.removeListener('change:delta', onChange);
      }

      spark.write({
        name: app.config.name,
        version: app.config.version,
        timestamp: new Date(),
        self: app.selfId,
        roles: ["master", "main"]
      })

    });

    primus.on('disconnection', function(spark) {
      spark.onDisconnect();
      debug(spark.id + " disconnected");
    });
  };

  api.stop = function() {
    if (primus.destroy && started) {
      debug("Destroying primus...");
      primus.destroy({
        close: false,
        timeout: 500
      });
    }
  };

  return api;
};

function normalizeDelta(delta) {
  return flatMap(delta.updates, normalizeUpdate).map(function(update) {
    return {
      context: delta.context,
      updates: [update]
    }
  })
};

function normalizeUpdate(update) {
  return update.values.map(function(value) {
    return {
      source: update.source,
      values: [value]
    }
  })
}
<|endoftext|>