<commit_before>/** Use buttons instead of the scrollbar.
 **
 ** This was inspired by Samich's answer on stackoverflow.com, here:
 ** http://stackoverflow.com/a/8329376/1378264
 **
 ** Similar plugins:
 ** http://logicbox.net/jquery/simplyscroll/
 ** https://github.com/mikecao/jquery-scrollable
 **
 ** Copyright (c) 2013 Philipp Zedler <philipp@neue-musik.com>
 ** Licensed under the MIT license
 **
 **/

(function ($) {

    "use strict";

    var defaults = {
            'up': undefined,
            'down': undefined,
            'velocity': 300, // Pixel per second
            'maxDuration': 2000 // Maximum duration for scrolling
        },
        upButton = '
Up
',
        downButton = '
Down
',
        ScrollableArea = function ($content, options) {
            this.$content = $content;
            this.$container = $content.parent();
            this.velocity = options.velocity;
            this.maxDuration = options.maxDuration;
            this.setButtons(options);
            this.wrapContent();
            this.setExtraHeight();
            this.$content.data('scroll-buttons-active', true);
            this.$content.data('scrollable-area', this);
        };
    ScrollableArea.prototype = {
        setExtraHeight: function () {
            var extraHeight = this.getContentHeight() - this.$container.height();
            this.extraHeight = extraHeight;
        },
        getContentHeight: function () {
            return this.$wrapper.prop('scrollHeight');
            /** TODO: Check if this works OK on firefox, see here:
             ** http://stackoverflow.com/a/15033226/1378264
             **/
        },
        getScrollUpDuration: function () {
            var scrollLength = this.$wrapper.prop('scrollTop');
            return this.getScrollDuration(scrollLength);
        },
        getScrollDownDuration: function () {
            var scrollLength = this.getContentHeight() - this.$wrapper.prop('scrollTop');
            return this.getScrollDuration(scrollLength);
        },
        getScrollDuration: function (scrollLength) {
            var scrollDuration = scrollLength / this.velocity * 1000;
            return Math.min(scrollDuration, this.maxDuration);
        },
        activateScrolling: function () {
            var thisScrollableArea = this;
            this.$down.hover(function () {
                thisScrollableArea.scrollDown();
            }, function () {
                thisScrollableArea.$wrapper.stop();
            });
            this.$up.hover(function () {
                thisScrollableArea.scrollUp();
            }, function () {
                thisScrollableArea.$wrapper.stop();
            });
        },
        setButtons: function (options) {
            if (options.up === undefined) {
                this.$up = $(upButton);
                this.$container.append(this.$up);
            } else {
                this.$up = this.$container.find(options.up);
            }
            if (options.down === undefined) {
                this.$down = $(downButton);
                this.$container.append(this.$down);
            } else {
                this.$down = this.$container.find(options.down);
            }
        },
        displayButtons: function () {
            if (this.extraHeight > 0) {
                this.$down.fadeIn();
                this.$up.fadeIn();
            } else {
                this.$down.fadeOut();
                this.$up.fadeOut();
            }
        },
        scrollDown: function () {
            this.scrollTo(this.extraHeight, this.getScrollDownDuration());
        },
        scrollUp: function () {
            this.scrollTo(0, this.getScrollUpDuration());
        },
        scrollTo: function (position, duration) {
            this.$wrapper.animate({scrollTop: position}, duration);
        },
        wrapContent: function () {
            this.$content.wrap('
');
            this.$wrapper = this.$content.parent();
            this.$container.css({overflow: 'hidden', position: 'relative' });
            this.$wrapper.css({
                height: this.$container.height(),
                width: this.$container.width() + 20,
                overflow: 'auto',
                position: 'absolute',
                top: 0,
                left: 0
            });
        },
        reload: function () {
            this.setExtraHeight();
            this.displayButtons();
        }
    };
    $.fn.scrollButtons = function (options) {
        if (options === 'reload') {
            if ($(this).data('scroll-buttons-active') === true) {
                $(this).data('scrollable-area').reload();
            }
        } else {
            var settings = $.extend({}, defaults, options),
                scrollableArea = new ScrollableArea($(this), settings);
            scrollableArea.activateScrolling();
            scrollableArea.displayButtons();
        }
        return this;
    };
}(jQuery));
<commit_msg>Draft code for touchscreens added which does not work yet.<commit_after>/** Use buttons instead of the scrollbar.
 **
 ** This was inspired by Samich's answer on stackoverflow.com, here:
 ** http://stackoverflow.com/a/8329376/1378264
 **
 ** Similar plugins:
 ** http://logicbox.net/jquery/simplyscroll/
 ** https://github.com/mikecao/jquery-scrollable
 **
 ** Copyright (c) 2013 Philipp Zedler <philipp@neue-musik.com>
 ** Licensed under the MIT license
 **
 **/

(function ($) {

    "use strict";

    var defaults = {
            'up': undefined,
            'down': undefined,
            'velocity': 300, // Pixel per second
            'maxDuration': 2000 // Maximum duration for scrolling
        },
        upButton = '
Up
',
        downButton = '
Down
',
        ScrollableArea = function ($content, options) {
            this.$content = $content;
            this.$container = $content.parent();
            this.velocity = options.velocity;
            this.maxDuration = options.maxDuration;
            this.setButtons(options);
            this.wrapContent();
            this.setExtraHeight();
            this.$content.data('scroll-buttons-active', true);
            this.$content.data('scrollable-area', this);
        };
    ScrollableArea.prototype = {
        setExtraHeight: function () {
            var extraHeight = this.getContentHeight() - this.$container.height();
            this.extraHeight = extraHeight;
        },
        getContentHeight: function () {
            return this.$wrapper.prop('scrollHeight');
            /** TODO: Check if this works OK on firefox, see here:
             ** http://stackoverflow.com/a/15033226/1378264
             **/
        },
        getScrollUpDuration: function () {
            var scrollLength = this.$wrapper.prop('scrollTop');
            return this.getScrollDuration(scrollLength);
        },
        getScrollDownDuration: function () {
            var scrollLength = this.getContentHeight() - this.$wrapper.prop('scrollTop');
            return this.getScrollDuration(scrollLength);
        },
        getScrollDuration: function (scrollLength) {
            var scrollDuration = scrollLength / this.velocity * 1000;
            return Math.min(scrollDuration, this.maxDuration);
        },
        activateScrolling: function () {
            var thisScrollableArea = this;
            this.$down.hover(function () {
                thisScrollableArea.scrollDown();
            }, function () {
                thisScrollableArea.$wrapper.stop();
            });
            this.$up.hover(function () {
                thisScrollableArea.scrollUp();
            }, function () {
                thisScrollableArea.$wrapper.stop();
            });
            // TODO: This does not work yet.
            //this.activateScrollingForTouchscreens();
        },
        activateScrollingForTouchscreens: function () {
            var thisScrollableArea = this;
            this.$down.bind('touchstart', function () {
                thisScrollableArea.scrollDown();
            });
            this.$down.bind('touched', function () {
                thisScrollableArea.$wrapper.stop();
            });
            this.$up.bind('touchstart', function () {
                thisScrollableArea.scrollUp();
            });
            this.$up.bind('touched', function () {
                thisScrollableArea.$wrapper.stop();
            });
        },
        setButtons: function (options) {
            if (options.up === undefined) {
                this.$up = $(upButton);
                this.$container.append(this.$up);
            } else {
                this.$up = this.$container.find(options.up);
            }
            if (options.down === undefined) {
                this.$down = $(downButton);
                this.$container.append(this.$down);
            } else {
                this.$down = this.$container.find(options.down);
            }
        },
        displayButtons: function () {
            if (this.extraHeight > 0) {
                this.$down.fadeIn();
                this.$up.fadeIn();
            } else {
                this.$down.fadeOut();
                this.$up.fadeOut();
            }
        },
        scrollDown: function () {
            this.scrollTo(this.extraHeight, this.getScrollDownDuration());
        },
        scrollUp: function () {
            this.scrollTo(0, this.getScrollUpDuration());
        },
        scrollTo: function (position, duration) {
            this.$wrapper.animate({scrollTop: position}, duration);
        },
        wrapContent: function () {
            this.$content.wrap('
');
            this.$wrapper = this.$content.parent();
            this.$container.css({overflow: 'hidden', position: 'relative' });
            this.$wrapper.css({
                height: this.$container.height(),
                width: this.$container.width() + 20,
                overflow: 'auto',
                position: 'absolute',
                top: 0,
                left: 0
            });
        },
        reload: function () {
            this.setExtraHeight();
            this.displayButtons();
        }
    };
    $.fn.scrollButtons = function (options) {
        if (options === 'reload') {
            if ($(this).data('scroll-buttons-active') === true) {
                $(this).data('scrollable-area').reload();
            }
        } else {
            var settings = $.extend({}, defaults, options),
                scrollableArea = new ScrollableArea($(this), settings);
            scrollableArea.activateScrolling();
            scrollableArea.displayButtons();
        }
        return this;
    };
}(jQuery));
<|endoftext|>


<commit_before>#!/usr/bin/env node

/*
 * tlddoc-util
 * https://github.com/NKjoep/tlddoc-util
 *
 * Copyright (c) 2014 Andrea Dessì
 * Licensed under the MIT license.
 */

var _ = require('underscore');
var argv = require('minimist')(process.argv.slice(2));
var fs = require('fs');
var handlebars = require('handlebars');
var parseString = require('xml2js').parseString;
var p = require('xml2js').parseString;
var util = require('util');
var path = require('path');

var basepath = __dirname;

var files = argv._;
var usage = function(){
	console.log("\nUsage:");
	console.log("\tnode tlddoc-util.js [-o outputfolder] [--output=outputfolder] file [<file> ...]");
	process.exit();
}

var outputfolder = 'generated-docs';

if(argv.o || argv.output) {
	outputfolder = argv.o||argv.output;
}

if (files.length==0||argv.h||argv.help) {
	usage();
	process.exit(1);
}

handlebars.registerHelper("js-string", function(string) {
	if(string===undefined) return ""
	if (util.isArray(string)) string = string.join('');
	return string.toString().replace(/\n/g, '').replace(/\r/g, '').replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/\s+/g, " ").replace(/^\s+/g, '').replace(/\s+$/g, '');
});

handlebars.registerHelper("cleanString", function(string) {
	if(string===undefined) return ""
	if (util.isArray(string)) string = string.join('');
	return string.toString().replace(/\n/g, '').replace(/\r/g, '').replace(/\s+/g, " ").replace(/^\s+/g, '').replace(/\s+$/g, '');
});

handlebars.registerHelper("scrollspySanitazer", function(string) {
	if(string===undefined) return ""
	if (util.isArray(string)) string = string.join('');
	return string.toString().replace(/\n/g, '').replace(/\r/g, '').replace(/\s+/g, " ").replace(/^\s+/g, '').replace(/\s+$/g, '').replace(/\./g, '');
});

var processedTagLib = [];

var processTagLib = function(filepath, callbackExecutor, index) {
	var xml = fs.readFileSync(filepath);

	parseString(xml, function (err, result) {
			if(err) {
				console.log('error processing: '+filepath);
				return;
			}
			var extractedTaglibrary = result.taglib;
			callbackExecutor(extractedTaglibrary, index);
	});

};

var createMainIndex = function() {
	processedTagLib = processedTagLib.sort(function(a, b){
		if ((a['short-name']).toString().toLowerCase() > (b['short-name']).toString().toLowerCase()) {
			return 1;
		}
		if ((a['short-name']).toString().toLowerCase() < (b['short-name']).toString().toLowerCase()) {
			return -1;
		}
		return 0;
	});
	console.log('sorted tag libraries...');
	try { fs.mkdirSync(outputfolder); } catch (e) {}

	templateSource = fs.readFileSync(basepath+"/templates/tag.js.tpl", {encoding: 'utf-8'});
	template = handlebars.compile(templateSource);
	compiledString = template({processedTagLib: processedTagLib});
	fs.writeFileSync(outputfolder+'/tag.js', compiledString, {flag: 'w'});
	console.log('generated javascript helper...');

	templateSource = fs.readFileSync(basepath+"/templates/index.html.tpl", {encoding: 'utf-8'});
	template = handlebars.compile(templateSource);
	compiledString = template({processedTagLib: processedTagLib});
	fs.writeFileSync(outputfolder+'/index.html', compiledString, {flag: 'w'});
	console.log('created index.html...');
	console.log('finish! Your documentation is ready at: ' + fs.realpathSync(path.normalize(outputfolder + '/index.html')));
	process.exit(0);
}

var reach = function(taglib, index) {
	processedTagLib.push(taglib);
	if(files.length-1==index) {
		createMainIndex();
	}
}

_.each(files, function(item, index) {
	console.log('parsing: '+item + ' ...')
	processTagLib(item, reach, index);
});
<commit_msg>usage sucks: fixed.<commit_after>#!/usr/bin/env node

/*
 * tlddoc-util
 * https://github.com/NKjoep/tlddoc-util
 *
 * Copyright (c) 2014 Andrea Dessì
 * Licensed under the MIT license.
 */

var _ = require('underscore');
var argv = require('minimist')(process.argv.slice(2));
var fs = require('fs');
var handlebars = require('handlebars');
var parseString = require('xml2js').parseString;
var p = require('xml2js').parseString;
var util = require('util');
var path = require('path');

var basepath = __dirname;

var files = argv._;
var usage = function(){
	console.log("\nUsage:");
	console.log("\ttlddoc-util.js [-o outputfolder] [--output=outputfolder] file [<file> ...]");
	process.exit();
}

var outputfolder = 'generated-docs';

if(argv.o || argv.output) {
	outputfolder = argv.o||argv.output;
}

if (files.length==0||argv.h||argv.help) {
	usage();
	process.exit(1);
}

handlebars.registerHelper("js-string", function(string) {
	if(string===undefined) return ""
	if (util.isArray(string)) string = string.join('');
	return string.toString().replace(/\n/g, '').replace(/\r/g, '').replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/\s+/g, " ").replace(/^\s+/g, '').replace(/\s+$/g, '');
});

handlebars.registerHelper("cleanString", function(string) {
	if(string===undefined) return ""
	if (util.isArray(string)) string = string.join('');
	return string.toString().replace(/\n/g, '').replace(/\r/g, '').replace(/\s+/g, " ").replace(/^\s+/g, '').replace(/\s+$/g, '');
});

handlebars.registerHelper("scrollspySanitazer", function(string) {
	if(string===undefined) return ""
	if (util.isArray(string)) string = string.join('');
	return string.toString().replace(/\n/g, '').replace(/\r/g, '').replace(/\s+/g, " ").replace(/^\s+/g, '').replace(/\s+$/g, '').replace(/\./g, '');
});

var processedTagLib = [];

var processTagLib = function(filepath, callbackExecutor, index) {
	var xml = fs.readFileSync(filepath);

	parseString(xml, function (err, result) {
			if(err) {
				console.log('error processing: '+filepath);
				return;
			}
			var extractedTaglibrary = result.taglib;
			callbackExecutor(extractedTaglibrary, index);
	});

};

var createMainIndex = function() {
	processedTagLib = processedTagLib.sort(function(a, b){
		if ((a['short-name']).toString().toLowerCase() > (b['short-name']).toString().toLowerCase()) {
			return 1;
		}
		if ((a['short-name']).toString().toLowerCase() < (b['short-name']).toString().toLowerCase()) {
			return -1;
		}
		return 0;
	});
	console.log('sorted tag libraries...');
	try { fs.mkdirSync(outputfolder); } catch (e) {}

	templateSource = fs.readFileSync(basepath+"/templates/tag.js.tpl", {encoding: 'utf-8'});
	template = handlebars.compile(templateSource);
	compiledString = template({processedTagLib: processedTagLib});
	fs.writeFileSync(outputfolder+'/tag.js', compiledString, {flag: 'w'});
	console.log('generated javascript helper...');

	templateSource = fs.readFileSync(basepath+"/templates/index.html.tpl", {encoding: 'utf-8'});
	template = handlebars.compile(templateSource);
	compiledString = template({processedTagLib: processedTagLib});
	fs.writeFileSync(outputfolder+'/index.html', compiledString, {flag: 'w'});
	console.log('created index.html...');
	console.log('finish! Your documentation is ready at: ' + fs.realpathSync(path.normalize(outputfolder + '/index.html')));
	process.exit(0);
}

var reach = function(taglib, index) {
	processedTagLib.push(taglib);
	if(files.length-1==index) {
		createMainIndex();
	}
}

_.each(files, function(item, index) {
	console.log('parsing: '+item + ' ...')
	processTagLib(item, reach, index);
});
<|endoftext|>


<commit_before>;( function( $ ){

	$.fn.extend( {
		scrollbarNav: function( options ){

			this.defaults = {
				topOffset : 10
			};

			var settings = $.extend( {}, this.defaults, options );
			return this.each( function() {

				var $this = $( this );

				//DOM 结构创建
				var tempDOM = $('

');
				var bodyHeight = $('body').height() + 40;
				$this.find('h1,h2,h3,h4,h5,h6')
					.each(function(index, elem) {
						var itemOffsetTop = $(elem).offset().top - parseInt(settings.topOffset);
						var windowHeight = $(window).height();
						//计算 item 在边栏的位置
						var scrollbarOffsetTop = (itemOffsetTop / bodyHeight) * windowHeight;
						var navItem = '
'+$(elem).text()+'
';
						tempDOM.append(navItem);
				});
				$('body').append(tempDOM);

				//单击滚动功能实现
				$('body').on('click','.scroll-nav li',function() {
					var offsetTop = $(this).data('offset-top');
					$('body').animate({scrollTop: offsetTop},200);
				});

			});
		}
	});

})( jQuery );<commit_msg>use window onload<commit_after>;( function( $ ){

	$.fn.extend( {
		scrollbarNav: function( options ){

			this.defaults = {
				topOffset : 10
			};

			var settings = $.extend( {}, this.defaults, options );
			return this.each( function() {

				$(window).load(function() {

					var $this = $( this );

					//DOM 结构创建
					var tempDOM = $('

');
					var bodyHeight = $('body').height() + 40;
					$this.find('h1,h2,h3,h4,h5,h6')
						.each(function(index, elem) {
							var itemOffsetTop = $(elem).offset().top - parseInt(settings.topOffset);
							var windowHeight = $(window).height();
							//计算 item 在边栏的位置
							var scrollbarOffsetTop = (itemOffsetTop / bodyHeight) * windowHeight;
							var navItem = '
'+$(elem).text()+'
';
							tempDOM.append(navItem);
					});
					$('body').append(tempDOM);

					//单击滚动功能实现
					$('body').on('click','.scroll-nav li',function() {
						var offsetTop = $(this).data('offset-top');
						$('body').animate({scrollTop: offsetTop},200);
					});

				});

			});
		}
	});

})( jQuery );<|endoftext|>


<commit_before>// # tool-stream
// > Node.js module for working with Streams of objects.
// >
// > doi: [?](http://doi.org/)
// > author: [Bruno Vieira](http://bmpvieira.com)
// > email: <mail@bmpvieira.com>
// > license: [MIT](https://raw.githubusercontent.com/bmpvieira/tool-stream/master/LICENSE)
//
// ---
//
// ## Usage
// These methods are Streams that work with Streams of objects/strings.
// If you are using Streams that output Buffers from JSON data, you can easily convert to
// objects with JSONStream.
// Example:
//
//     npm install tool-stream JSONStream request
//
//     var tool = require('tool-stream')
//     var JSONStream = require('JSONStream')
//     var request = require('request')
//
//     var query = 'http://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term=asthma&retmode=json'
//
//     request({uri: query, json: true})
//     .pipe(JSONStream.parse())
//     .pipe(tool.extractProperty('esearchresult.idlist'))
//     .pipe(tool.arraySplit())
//     .pipe(JSONStream.stringify())
//     .pipe(process.stdout)
//
//     => [ "24908147",
//          "24907978",
//          [...],
//          "24903131" ]
//
// For more examples of real use cases, checkout the module
// [bionode-ncbi](http://github.com/bionode/bionode-ncbi), which depends on many
// methods from this module.

var through = require('through2')
var async = require('async')
var bops = require('bops')
var uniq = require('unique-stream')
var flat = require('flat')
var util = require('util')
var nestedProperty = require('nested-property')
var xml2jsParser = require('xml2js').Parser
var xml2js = new xml2jsParser({mergeAttrs: true, explicitArray: false, explicitRoot: false}).parseString
var xml2jsArray = new xml2jsParser({mergeAttrs: true, explicitArray: true, explicitRoot: false}).parseString

module.exports = exports = tool = new ToolStream()

function ToolStream() {
  return this
}

// If input is an array or an object as associative array, output each value.
ToolStream.prototype.arraySplit = function() {
  return through.obj(transform)
  function transform(obj, enc, next) {
    var self = this
    Object.keys(obj).forEach(pushValue)
    function pushValue(key) { self.push(obj[key]) }
    next()
  }
}

// Attach to a value from another object (e.g., from a key store)
ToolStream.prototype.attachStoredValue = function(outsideObj, streamObjSrcProp, streamObjDestProp) {
  return through.obj(cb)
  function cb(obj, enc, next) {
    var outsideValue = outsideObj[obj[streamObjSrcProp]]
    obj[streamObjDestProp] = outsideValue
    this.push(obj)
    next()
  }
}

// Copy external object and attach input values to specified property, outputs new object.
ToolStream.prototype.attachToObject = function(object, property) {
  return through.obj(cb)
  function cb(obj, enc, next) {
    var extend = util._extend
    var copy = extend(object)
    copy[property] = obj
    this.push(copy)
    next()
  }
}

// Only outputs objects with property value equal to match
ToolStream.prototype.collectMatch = function(property, match) {
  return through.obj(transform)
  function transform(obj, enc, next) {
    var self = this
    if (match === tool.getValue(obj, property)) { self.push(obj) }
    next()
  }
}

// Outputs a triple object using specified properties for subject/object, and string for predicate.
ToolStream.prototype.createTriple = function(subjectKey, predicate, objectKey) {
  return through.obj(cb)
  function cb(obj, enc, next) {
    var subject = tool.getValue(obj, subjectKey)
    var object = tool.getValue(obj, objectKey)
    var triple = { subject: subject, predicate: predicate, object: object }
    this.push(triple)
    next()
  }
}

// Deletes specified property
ToolStream.prototype.deleteProperty = function(property) {
  return through.obj(transform)
  function transform(obj, enc, next) {
    delete obj[property]
    this.push(obj)
    next()
  }
}

// If specified property isn't an array, make it an array
ToolStream.prototype.ensureIsArray = function(property) {
  return through.obj(cb)
  function cb(obj, enc, next) {
    var value = nestedProperty.get(obj, property)
    if (!Array.isArray(value)) { nestedProperty.set(obj, property, [value]) }
    this.push(obj)
    next()
  }
}

// Only pass value from a specified key in object
ToolStream.prototype.extractProperty = function(property, unique) {
  var uniqStream = uniq()
  var extractStream = through.obj(extract)
  var extractUniqueStream = through.obj(extractUnique)

  var attached = false

  var stream = unique ? extractUniqueStream : extractStream
  return stream

  function extractUnique(obj, enc, next) {
    var self = this
    extractStream.write(obj)
    if (!attached) {
      extractStream.pipe(uniqStream)
      uniqStream.on('data', function(data) {
        self.push(data)
      })
      attached = true
    }
    next()
  }
  function extract(obj, enc, next) {
    var value = tool.getValue(obj, property)
    this.push(value)
    next()
  }
}

// Filters objects with property value equal to match
ToolStream.prototype.filterMatch = function(property, match) {
  return through.obj(transform)
  function transform(obj, enc, next) {
    var self = this
    if (match !== tool.getValue(obj, property)) { self.push(obj) }
    next()
  }
}

// Removes nested objects with property value equal to match from input object array.
ToolStream.prototype.filterObjectsArray = function(property, match, path) {
  return through.obj(transform)
  function transform(obj, enc, next) {
    var self = this
    var objectsArray = path ? nestedProperty.get(obj, path) : obj
    objectsArray.forEach(filterMatch)
    function filterMatch(arrayObj, i) {
      if (arrayObj[property] === match) { objectsArray.splice(i, 1) }
    }
    if (path) { nestedProperty.set(obj, path, objectsArray) }
    self.push(obj)
    next()
  }
}

// Flats the object and looks for values matching regex, attaching them to specified property.
ToolStream.prototype.grepObject = function(regex, propertyToSave, data) {
  var stream = through.obj(cb)
  if (data) { stream.write(data) }
  return stream
  function cb(obj, enc, next) {
    var flatObj = flat.flatten(obj)
    var relevantData = []
    Object.keys(flatObj).forEach(checkRelevant)
    function checkRelevant(key) {
      var value = flatObj[key].toString()
      if (value.match(regex)) {
        relevantData.push(value)
      }
    }
    uniqueRelevantData = relevantData.filter(function(elem, pos, self) {
      return self.indexOf(elem) == pos;
    })
    obj[propertyToSave] = uniqueRelevantData
    this.push(obj)
    next()
  }
}

// Converts JSON to Buffer
ToolStream.prototype.JSONToBuffer = function() {
  return through.obj(cb)
  function cb(obj, enc, next) {
  this.push(bops.from(JSON.stringify(obj) + '\n'))
  next()
  }
}

// Stores in another object. Can do interpolation of values from stream for keys.
// If the destination property isn't empty or is an array, it will push the object
ToolStream.prototype.storeToObject = function(storeObject, storeProperty, streamObjectValue) {
  return through.obj(cb)
  function cb(obj, enc, next) {
    var store = storeObject
    var previous
    var regexMatchDotsOutsideDoubleBrackets = /\.(?=(?:[^\}\}]|\{\{[^\}\}]*\}\})*$)/
    var storePropertyArgs = storeProperty.split(regexMatchDotsOutsideDoubleBrackets)

    storePropertyArgs.forEach(setProperty)
    function setProperty(storeProperty) {
      if (previous) { store = store[previous] }
      // Check if store property needs interpolation of value from object from stream
      if (tool.startsWith(storeProperty, '{{') && tool.endsWith(storeProperty, '}}')) {
        var storeKey = obj
        var streamObjectProperty = storeProperty.slice(2, -2).split('.')
        streamObjectProperty.forEach(moveDeeper)
        function moveDeeper(streamObjectKey) {
          storeKey = storeKey[streamObjectKey]
        }
      }
      // Otherwise, no interpolation needed
      else {
        storeKey = storeProperty
      }
      if (typeof store[storeKey] === 'undefined') {
        store[storeKey] = {}
      }
      previous = storeKey
    }

    var dataToStore = streamObjectValue ? obj[streamObjectValue] : obj

    if (Array.isArray(store[previous])) {
      store[previous].push(dataToStore)
    }
    else if (store[previous] === '' || typeof store[previous] === 'object' && Object.keys(store[previous]).length === 0) {
      store[previous] = dataToStore
    }
    else {
      var previousData = store[previous]
      store[previous] = []
      store[previous].push(previousData)
      store[previous].push(dataToStore)
    }

    this.push(obj)
    next()
  }
}

// Outputs the same object multiple times for each value in specified array from property.
// Copies of the object will only differ by those values.
ToolStream.prototype.splitObjectByArrayProperty = function(propertyToSplit, splitedPropertyName) {
  return through.obj(cb)
  function cb(obj, enc, next) {
    var self = this
    var values = tool.getValue(obj, propertyToSplit)
    values.forEach(pushSplit)
    function pushSplit(value) {
      obj[splitedPropertyName] = value
      self.push(obj)
    }
    next()
  }
}

// Unflats an object that has properties in JSON (like when coming from Dat)
ToolStream.prototype.unflatTabularObject = function() {
  return through.obj(transform)
  function transform(obj, enc, next) {
    Object.keys(obj).forEach(parse)
    function parse(key) {
      var parsed
      try { parsed = JSON.parse(obj[key]) }
      catch(e) {}
      if (parsed) { obj[key] = parsed }
    }
    this.push(obj)
    next()
  }
}

// Converts XML input to JS, takes a Boolean to set explicitArray option of parser.
ToolStream.prototype.XMLToJS = function(explicitArray) {
  return through.obj(transform)
  function transform(obj, enc, next) {
    var self = this
    var parser = explicitArray ? xml2jsArray : xml2js
    parser(obj, gotStringParsed)
    function gotStringParsed(err, data) {
      if (err) { return self.emit('error', err) }
      self.push(data)
      next()
    }
  }
}

// ## Parse XML properties to JS
// Takes and array of strings representing properties of objects being passed and
// returns objects with those properties parsed from XML to JSON:
//
//     var ncbi = require('bionode-ncbi')
//     var tool = require('tool-stream')
//     var searchStream = ncbi.search('sra', 'solenopsis invicta')
//     var parseStream = tool.XMLToJSProperties(['expxml', 'runs'])
//     searchStream.pipe(parseStream).on('data', console.log)
//     => { uid: '225471',
//          expxml:
//           { root:
//              { Summary: [Object],
//                Submitter: [Object],
//                Experiment: [Object],
//                Study: [Object],
//                Organism: [Object],
//                Sample: [Object],
//                Instrument: [Object],
//                Library_descriptor: [Object],
//                Biosample: [Object],
//                Bioproject: [Object] } },
//          runs: { root: { Run: [Object] } },
//          extlinks: '    ',
//          createdate: '2013/03/28',
//          updatedate: '2013/09/23' }
//        [...]

ToolStream.prototype.XMLToJSProperties = function(properties) {
  return through.obj(parser)
  function parser(obj, enc, next) {
    var self = this
    async.forEach(properties, parseXMLProperty, gotAllParsed)
    function gotAllParsed(err) {
      if (err) { return self.emit('error', err) }
      self.push(obj)
      next()
    }
    function parseXMLProperty(property, callback) {
      parseXMLString(obj[property], gotPropertyParsed)
      function gotPropertyParsed(js){
        obj[property] = js
        callback()
      }
    }
  }
  function parseXMLString(xmlString, callback) {
    var xml = '<root>'
      + xmlString
      .replace(/</g, '<')
      .replace(/>/g, '>')
      + '</root>'
    xml2js(xml, gotStringParsed)
    function gotStringParsed(err, js) {
      if (err) { return self.emit('error', err) }
      callback(js)
    }
  }
}

// Util to check if str starts with prefix
ToolStream.prototype.startsWith = function(str, prefix) {
  return str.indexOf(prefix) === 0
}

// Util to check if str ends with suffix
ToolStream.prototype.endsWith = function(str, suffix) {
  return str.indexOf(suffix, str.length - suffix.length) !== -1
}

// Get nested property. Can also go inside JSON strings (and thus differ from nestedProperty).
ToolStream.prototype.getValue = function(obj, path) {
  var value = obj
  path.split('.').forEach(moveDeeper)
  function moveDeeper(key) {
    if (typeof value === 'string') { value = JSON.parse(value) }
    value = value[key]
  }
  return value
}

// Set nested property. Can also go inside JSON strings (and thus differ from nestedProperty).
ToolStream.prototype.setValue = function(obj, path, value) {
  var value = obj
  var parent
  path.split('.').forEach(moveDeeper)
  function moveDeeper(key) {
    if (typeof value === 'string') { value = JSON.parse(value) }
    parent
    value = value[key]
  }
  return value
}
<commit_msg>Fix setValue and add insertProperty<commit_after>// # tool-stream
// > Node.js module for working with Streams of objects.
// >
// > doi: [?](http://doi.org/)
// > author: [Bruno Vieira](http://bmpvieira.com)
// > email: <mail@bmpvieira.com>
// > license: [MIT](https://raw.githubusercontent.com/bmpvieira/tool-stream/master/LICENSE)
//
// ---
//
// ## Usage
// These methods are Streams that work with Streams of objects/strings.
// If you are using Streams that output Buffers from JSON data, you can easily convert to
// objects with JSONStream.
// Example:
//
//     npm install tool-stream JSONStream request
//
//     var tool = require('tool-stream')
//     var JSONStream = require('JSONStream')
//     var request = require('request')
//
//     var query = 'http://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term=asthma&retmode=json'
//
//     request({uri: query, json: true})
//     .pipe(JSONStream.parse())
//     .pipe(tool.extractProperty('esearchresult.idlist'))
//     .pipe(tool.arraySplit())
//     .pipe(JSONStream.stringify())
//     .pipe(process.stdout)
//
//     => [ "24908147",
//          "24907978",
//          [...],
//          "24903131" ]
//
// For more examples of real use cases, checkout the module
// [bionode-ncbi](http://github.com/bionode/bionode-ncbi), which depends on many
// methods from this module.

var through = require('through2')
var async = require('async')
var bops = require('bops')
var uniq = require('unique-stream')
var flat = require('flat')
var util = require('util')
var nestedProperty = require('nested-property')
var xml2jsParser = require('xml2js').Parser
var xml2js = new xml2jsParser({mergeAttrs: true, explicitArray: false, explicitRoot: false}).parseString
var xml2jsArray = new xml2jsParser({mergeAttrs: true, explicitArray: true, explicitRoot: false}).parseString

module.exports = exports = tool = new ToolStream()

function ToolStream() {
  return this
}

// If input is an array or an object as associative array, output each value.
ToolStream.prototype.arraySplit = function() {
  return through.obj(transform)
  function transform(obj, enc, next) {
    var self = this
    Object.keys(obj).forEach(pushValue)
    function pushValue(key) { self.push(obj[key]) }
    next()
  }
}

// Attach to a value from another object (e.g., from a key store)
ToolStream.prototype.attachStoredValue = function(outsideObj, streamObjSrcProp, streamObjDestProp) {
  return through.obj(cb)
  function cb(obj, enc, next) {
    var outsideValue = outsideObj[obj[streamObjSrcProp]]
    obj[streamObjDestProp] = outsideValue
    this.push(obj)
    next()
  }
}

// Copy external object and attach input values to specified property, outputs new object.
ToolStream.prototype.attachToObject = function(object, property) {
  return through.obj(cb)
  function cb(obj, enc, next) {
    var extend = util._extend
    var copy = extend(object)
    copy[property] = obj
    this.push(copy)
    next()
  }
}

// Only outputs objects with property value equal to match
ToolStream.prototype.collectMatch = function(property, match) {
  return through.obj(transform)
  function transform(obj, enc, next) {
    var self = this
    if (match === tool.getValue(obj, property)) { self.push(obj) }
    next()
  }
}

// Outputs a triple object using specified properties for subject/object, and string for predicate.
ToolStream.prototype.createTriple = function(subjectKey, predicate, objectKey) {
  return through.obj(cb)
  function cb(obj, enc, next) {
    var subject = tool.getValue(obj, subjectKey)
    var object = tool.getValue(obj, objectKey)
    var triple = { subject: subject, predicate: predicate, object: object }
    this.push(triple)
    next()
  }
}

// Deletes specified property
ToolStream.prototype.deleteProperty = function(property) {
  return through.obj(transform)
  function transform(obj, enc, next) {
    delete obj[property]
    this.push(obj)
    next()
  }
}

// If specified property isn't an array, make it an array
ToolStream.prototype.ensureIsArray = function(property) {
  return through.obj(cb)
  function cb(obj, enc, next) {
    var value = nestedProperty.get(obj, property)
    if (!Array.isArray(value)) { nestedProperty.set(obj, property, [value]) }
    this.push(obj)
    next()
  }
}

// Only pass value from a specified key in object
ToolStream.prototype.extractProperty = function(property, unique) {
  var uniqStream = uniq()
  var extractStream = through.obj(extract)
  var extractUniqueStream = through.obj(extractUnique)

  var attached = false

  var stream = unique ? extractUniqueStream : extractStream
  return stream

  function extractUnique(obj, enc, next) {
    var self = this
    extractStream.write(obj)
    if (!attached) {
      extractStream.pipe(uniqStream)
      uniqStream.on('data', function(data) {
        self.push(data)
      })
      attached = true
    }
    next()
  }
  function extract(obj, enc, next) {
    var value = tool.getValue(obj, property)
    this.push(value)
    next()
  }
}

// Only pass value from a specified key in object
ToolStream.prototype.insertProperty = function(property, value) {
  return through.obj(transform)
  function transform(obj, enc, next) {
    this.push(tool.setValue(obj, property, value))
    next()
  }
}

// Filters objects with property value equal to match
ToolStream.prototype.filterMatch = function(property, match) {
  return through.obj(transform)
  function transform(obj, enc, next) {
    var self = this
    if (match !== tool.getValue(obj, property)) { self.push(obj) }
    next()
  }
}

// Removes nested objects with property value equal to match from input object array.
ToolStream.prototype.filterObjectsArray = function(property, match, path) {
  return through.obj(transform)
  function transform(obj, enc, next) {
    var self = this
    var objectsArray = path ? nestedProperty.get(obj, path) : obj
    objectsArray.forEach(filterMatch)
    function filterMatch(arrayObj, i) {
      if (arrayObj[property] === match) { objectsArray.splice(i, 1) }
    }
    if (path) { nestedProperty.set(obj, path, objectsArray) }
    self.push(obj)
    next()
  }
}

// Flats the object and looks for values matching regex, attaching them to specified property.
ToolStream.prototype.grepObject = function(regex, propertyToSave, data) {
  var stream = through.obj(cb)
  if (data) { stream.write(data) }
  return stream
  function cb(obj, enc, next) {
    var flatObj = flat.flatten(obj)
    var relevantData = []
    Object.keys(flatObj).forEach(checkRelevant)
    function checkRelevant(key) {
      var value = flatObj[key].toString()
      if (value.match(regex)) {
        relevantData.push(value)
      }
    }
    uniqueRelevantData = relevantData.filter(function(elem, pos, self) {
      return self.indexOf(elem) == pos;
    })
    obj[propertyToSave] = uniqueRelevantData
    this.push(obj)
    next()
  }
}

// Converts JSON to Buffer
ToolStream.prototype.JSONToBuffer = function() {
  return through.obj(cb)
  function cb(obj, enc, next) {
  this.push(bops.from(JSON.stringify(obj) + '\n'))
  next()
  }
}

// Stores in another object. Can do interpolation of values from stream for keys.
// If the destination property isn't empty or is an array, it will push the object
ToolStream.prototype.storeToObject = function(storeObject, storeProperty, streamObjectValue) {
  return through.obj(cb)
  function cb(obj, enc, next) {
    var store = storeObject
    var previous
    var regexMatchDotsOutsideDoubleBrackets = /\.(?=(?:[^\}\}]|\{\{[^\}\}]*\}\})*$)/
    var storePropertyArgs = storeProperty.split(regexMatchDotsOutsideDoubleBrackets)

    storePropertyArgs.forEach(setProperty)
    function setProperty(storeProperty) {
      if (previous) { store = store[previous] }
      // Check if store property needs interpolation of value from object from stream
      if (tool.startsWith(storeProperty, '{{') && tool.endsWith(storeProperty, '}}')) {
        var storeKey = obj
        var streamObjectProperty = storeProperty.slice(2, -2).split('.')
        streamObjectProperty.forEach(moveDeeper)
        function moveDeeper(streamObjectKey) {
          storeKey = storeKey[streamObjectKey]
        }
      }
      // Otherwise, no interpolation needed
      else {
        storeKey = storeProperty
      }
      if (typeof store[storeKey] === 'undefined') {
        store[storeKey] = {}
      }
      previous = storeKey
    }

    var dataToStore = streamObjectValue ? obj[streamObjectValue] : obj

    if (Array.isArray(store[previous])) {
      store[previous].push(dataToStore)
    }
    else if (store[previous] === '' || typeof store[previous] === 'object' && Object.keys(store[previous]).length === 0) {
      store[previous] = dataToStore
    }
    else {
      var previousData = store[previous]
      store[previous] = []
      store[previous].push(previousData)
      store[previous].push(dataToStore)
    }

    this.push(obj)
    next()
  }
}

// Outputs the same object multiple times for each value in specified array from property.
// Copies of the object will only differ by those values.
ToolStream.prototype.splitObjectByArrayProperty = function(propertyToSplit, splitedPropertyName) {
  return through.obj(cb)
  function cb(obj, enc, next) {
    var self = this
    var values = tool.getValue(obj, propertyToSplit)
    values.forEach(pushSplit)
    function pushSplit(value) {
      obj[splitedPropertyName] = value
      self.push(obj)
    }
    next()
  }
}

// Unflats an object that has properties in JSON (like when coming from Dat)
ToolStream.prototype.unflatTabularObject = function() {
  return through.obj(transform)
  function transform(obj, enc, next) {
    Object.keys(obj).forEach(parse)
    function parse(key) {
      var parsed
      try { parsed = JSON.parse(obj[key]) }
      catch(e) {}
      if (parsed) { obj[key] = parsed }
    }
    this.push(obj)
    next()
  }
}

// Converts XML input to JS, takes a Boolean to set explicitArray option of parser.
ToolStream.prototype.XMLToJS = function(explicitArray) {
  return through.obj(transform)
  function transform(obj, enc, next) {
    var self = this
    var parser = explicitArray ? xml2jsArray : xml2js
    parser(obj, gotStringParsed)
    function gotStringParsed(err, data) {
      if (err) { return self.emit('error', err) }
      self.push(data)
      next()
    }
  }
}

// ## Parse XML properties to JS
// Takes and array of strings representing properties of objects being passed and
// returns objects with those properties parsed from XML to JSON:
//
//     var ncbi = require('bionode-ncbi')
//     var tool = require('tool-stream')
//     var searchStream = ncbi.search('sra', 'solenopsis invicta')
//     var parseStream = tool.XMLToJSProperties(['expxml', 'runs'])
//     searchStream.pipe(parseStream).on('data', console.log)
//     => { uid: '225471',
//          expxml:
//           { root:
//              { Summary: [Object],
//                Submitter: [Object],
//                Experiment: [Object],
//                Study: [Object],
//                Organism: [Object],
//                Sample: [Object],
//                Instrument: [Object],
//                Library_descriptor: [Object],
//                Biosample: [Object],
//                Bioproject: [Object] } },
//          runs: { root: { Run: [Object] } },
//          extlinks: '    ',
//          createdate: '2013/03/28',
//          updatedate: '2013/09/23' }
//        [...]

ToolStream.prototype.XMLToJSProperties = function(properties) {
  return through.obj(parser)
  function parser(obj, enc, next) {
    var self = this
    async.forEach(properties, parseXMLProperty, gotAllParsed)
    function gotAllParsed(err) {
      if (err) { return self.emit('error', err) }
      self.push(obj)
      next()
    }
    function parseXMLProperty(property, callback) {
      parseXMLString(obj[property], gotPropertyParsed)
      function gotPropertyParsed(js){
        obj[property] = js
        callback()
      }
    }
  }
  function parseXMLString(xmlString, callback) {
    var xml = '<root>'
      + xmlString
      .replace(/</g, '<')
      .replace(/>/g, '>')
      + '</root>'
    xml2js(xml, gotStringParsed)
    function gotStringParsed(err, js) {
      if (err) { return self.emit('error', err) }
      callback(js)
    }
  }
}

// Util to check if str starts with prefix
ToolStream.prototype.startsWith = function(str, prefix) {
  return str.indexOf(prefix) === 0
}

// Util to check if str ends with suffix
ToolStream.prototype.endsWith = function(str, suffix) {
  return str.indexOf(suffix, str.length - suffix.length) !== -1
}

// Get nested property. Can also go inside JSON strings (and thus differ from nestedProperty).
ToolStream.prototype.getValue = function(obj, path) {
  var value = obj
  path.split('.').forEach(moveDeeper)
  function moveDeeper(key) {
    if (typeof value === 'string') { value = JSON.parse(value) }
    value = value[key]
  }
  return value
}

// Set nested property. Can also go inside JSON strings (and thus differ from nestedProperty).
ToolStream.prototype.setValue = function(obj, path, newvalue) {
  var value = obj
  var parent
  path.split('.').forEach(moveDeeper)
  function moveDeeper(key) {
    if (typeof value === 'string') { value = JSON.parse(value) }
    parent = value
    value = value[key]
  }
  parent[path] = newvalue
  return parent
}
<|endoftext|>


<commit_before>$(function() {
  var loadFileFromSource = function(source, next) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', source, true);
    xhr.responseType = 'arraybuffer';
    xhr.send();

    xhr.onload = function() {
      var uint8array = new Uint8Array(xhr.response);
      var file = new Blob([uint8array], {type: 'video/webm'});
      next(file);
    };
  };

  var addFileToSourceBuffer = function(sourceBuffer, file, timestampOffset, next) {
    var reader = new FileReader();

    reader.onload = function(e) {
      sourceBuffer.timestampOffset = timestampOffset;
      sourceBuffer.appendBuffer(new Uint8Array(e.target.result));
      if (next) {
        next();
      }
    };

    reader.readAsArrayBuffer(file);
  };

  var addSourceToBuffer = function(sourceBuffer, source, timestampOffset, next) {
    loadFileFromSource(source, function(file) {
      addFileToSourceBuffer(sourceBuffer, file, timestampOffset, next);
    });
  };

  var addSourcesToMediaSource = function(mediaSource, specs, mimeType) {
    mediaSource.addEventListener('sourceopen', function() {
      var sourceBuffer = mediaSource.addSourceBuffer(mimeType);

      var initialSpec = specs.shift();
      var initialSource = initialSpec.source;
      var initialTimestampOffset = initialSpec.timestampOffset;

      addSourceToBuffer(sourceBuffer, initialSource, initialTimestampOffset, function() {
        sourceBuffer.addEventListener('updateend', function() {
          if (specs.length > 0) {
            var spec = specs.shift();
            var source = spec.source;
            var timestampOffset = spec.timestampOffset;

            addSourceToBuffer(sourceBuffer, source, timestampOffset);
          }
        });
      });
    });
  };

  var addSourcesToMediaElement = function(mediaElement, specs, mimeType) {
    var mediaSource = new MediaSource();
    mediaElement.src = window.URL.createObjectURL(mediaSource);
    addSourcesToMediaSource(mediaSource, specs, mimeType);
  };

  var assertTagName = function(jqueryElement, methodName, tagName) {
    if (jqueryElement.prop('tagName') !== tagName) {
      $.error('Called $.' + methodName + ' on a non ' + tagName + ' tag.');
    }
  };

  $.fn.videosequence = function(specs) {
    assertTagName(this, 'videosequence', 'VIDEO');
    addSourcesToMediaElement(this[0], specs, 'video/webm; codecs="vorbis,vp8"');
  };

  $.fn.audiosequence = function(specs) {
    assertTagName(this, 'audiosequence', 'AUDIO');
    addSourcesToMediaElement(this[0], specs, 'audio/webm; codecs="vorbis"');
  };
});
<commit_msg>Plugin should be chainable<commit_after>$(function() {
  var loadFileFromSource = function(source, next) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', source, true);
    xhr.responseType = 'arraybuffer';
    xhr.send();

    xhr.onload = function() {
      var uint8array = new Uint8Array(xhr.response);
      var file = new Blob([uint8array], {type: 'video/webm'});
      next(file);
    };
  };

  var addFileToSourceBuffer = function(sourceBuffer, file, timestampOffset, next) {
    var reader = new FileReader();

    reader.onload = function(e) {
      sourceBuffer.timestampOffset = timestampOffset;
      sourceBuffer.appendBuffer(new Uint8Array(e.target.result));
      if (next) {
        next();
      }
    };

    reader.readAsArrayBuffer(file);
  };

  var addSourceToBuffer = function(sourceBuffer, source, timestampOffset, next) {
    loadFileFromSource(source, function(file) {
      addFileToSourceBuffer(sourceBuffer, file, timestampOffset, next);
    });
  };

  var addSourcesToMediaSource = function(mediaSource, specs, mimeType) {
    mediaSource.addEventListener('sourceopen', function() {
      var sourceBuffer = mediaSource.addSourceBuffer(mimeType);

      var initialSpec = specs.shift();
      var initialSource = initialSpec.source;
      var initialTimestampOffset = initialSpec.timestampOffset;

      addSourceToBuffer(sourceBuffer, initialSource, initialTimestampOffset, function() {
        sourceBuffer.addEventListener('updateend', function() {
          if (specs.length > 0) {
            var spec = specs.shift();
            var source = spec.source;
            var timestampOffset = spec.timestampOffset;

            addSourceToBuffer(sourceBuffer, source, timestampOffset);
          }
        });
      });
    });
  };

  var addSourcesToMediaElement = function(mediaElement, specs, mimeType) {
    var mediaSource = new MediaSource();
    mediaElement.src = window.URL.createObjectURL(mediaSource);
    addSourcesToMediaSource(mediaSource, specs, mimeType);
  };

  var assertTagName = function(jqueryElement, methodName, tagName) {
    if (jqueryElement.prop('tagName') !== tagName) {
      $.error('Called $.' + methodName + ' on a non ' + tagName + ' tag.');
    }
  };

  var createSequence = function(tagName, jqueryElement, specs, codecInfo) {
    assertTagName(jqueryElement, tagName + 'sequence', tagName.toUpperCase());
    addSourcesToMediaElement(jqueryElement[0], specs, codecInfo);
    return jqueryElement;
  };

  $.fn.videosequence = function(specs) {
    return createSequence('video', this, specs, 'video/webm; codecs="vorbis,vp8"');
  };

  $.fn.audiosequence = function(specs) {
    return createSequence('audio', this, specs, 'audio/webm; codecs="vorbis"');
  };
});
<|endoftext|>


<commit_before>importScripts('jquery.nodom.min.js', 'underscore.min.js', 'underscore.min.js', 'SortedList.js', 'IntervalTree.js', 'CustomTracks.js');
var global = this;

if (!global.console || !global.console.log) {
  global.console = global.console || {};
  global.console.log = function() {
    global.postMessage({log: JSON.stringify(_.toArray(arguments))});
  };
}

var CustomTrackWorker = {
  _tracks: [],
  _throwErrors: false,
  parse: function(text, browserOpts) {
    throw {message: "boom"};
    var self = this,
      tracks = CustomTracks.parse(text, browserOpts);
    return _.map(tracks, function(t) {
      // we want to keep the track object in our private store, and delete the data from the copy that
      // is sent back over the fence, since it is expensive/impossible to serialize
      t.id = self._tracks.push(t) - 1;
      var serializable = _.extend({}, t);
      delete serializable.data;
      return serializable;
    });
  },
  prerender: function() {
    var args = _.toArray(arguments),
      id = _.first(args),
      track = this._tracks[id];
    track.prerender.apply(track, _.rest(args));
  },
  throwErrors: function(toggle) {
    this._throwErrors = toggle;
  }
};

global.addEventListener('message', function(e) {
  var data = e.data,
    callback = function(r) { global.postMessage({id: data.id, ret: JSON.stringify(r || null)}); },
    ret;

  if (CustomTrackWorker._throwErrors) {
    ret = CustomTrackWorker[data.op].apply(CustomTrackWorker, data.args.concat(callback));
  } else {
    try { ret = CustomTrackWorker[data.op].apply(CustomTrackWorker, data.args.concat(callback)); }
    catch (err) { global.postMessage({id: data.id, error: JSON.stringify({message: err.message})}); }
  }

  if (!_.isUndefined(ret)) { callback(ret); }
});<commit_msg>didn't mean to commit that line.<commit_after>importScripts('jquery.nodom.min.js', 'underscore.min.js', 'underscore.min.js', 'SortedList.js', 'IntervalTree.js', 'CustomTracks.js');
var global = this;

if (!global.console || !global.console.log) {
  global.console = global.console || {};
  global.console.log = function() {
    global.postMessage({log: JSON.stringify(_.toArray(arguments))});
  };
}

var CustomTrackWorker = {
  _tracks: [],
  _throwErrors: false,
  parse: function(text, browserOpts) {
    var self = this,
      tracks = CustomTracks.parse(text, browserOpts);
    return _.map(tracks, function(t) {
      // we want to keep the track object in our private store, and delete the data from the copy that
      // is sent back over the fence, since it is expensive/impossible to serialize
      t.id = self._tracks.push(t) - 1;
      var serializable = _.extend({}, t);
      delete serializable.data;
      return serializable;
    });
  },
  prerender: function() {
    var args = _.toArray(arguments),
      id = _.first(args),
      track = this._tracks[id];
    track.prerender.apply(track, _.rest(args));
  },
  throwErrors: function(toggle) {
    this._throwErrors = toggle;
  }
};

global.addEventListener('message', function(e) {
  var data = e.data,
    callback = function(r) { global.postMessage({id: data.id, ret: JSON.stringify(r || null)}); },
    ret;

  if (CustomTrackWorker._throwErrors) {
    ret = CustomTrackWorker[data.op].apply(CustomTrackWorker, data.args.concat(callback));
  } else {
    try { ret = CustomTrackWorker[data.op].apply(CustomTrackWorker, data.args.concat(callback)); }
    catch (err) { global.postMessage({id: data.id, error: JSON.stringify({message: err.message})}); }
  }

  if (!_.isUndefined(ret)) { callback(ret); }
});<|endoftext|>


<commit_before>/**
 * Copyright 2016 Netsend.
 *
 * This file is part of PersDB.
 *
 * PersDB is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Affero General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * PersDB is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with PersDB. If not, see <https://www.gnu.org/licenses/>.
 */

'use strict';

var programName = require('path').basename(__filename, '.js');

if (process.getuid() !== 0) {
  console.error('%s: execute as root', programName);
  process.exit(1);
}

if (typeof process.send !== 'function') {
  console.error('%s: use child_process.spawn to invoke this module', programName);
  process.exit(2);
}

var dns = require('dns');
var fs = require('fs');
var net = require('net');

var chroot = require('chroot');
var posix = require('posix');
var ssh = require('ssh2');

var logger = require('./logger');

var log; // used after receiving the log configuration

/**
 * Instantiate an ssh tunnel.
 *
 * Full FSM: init --> listen
 *
 * The first message emitted is "init" which signals that this process is ready to
 * receive the configuration data. File desciptors for the log config should be
 * sent in subsequent messages.
 *
 * Only a kill message can be sent to signal the end of the process.
 *
 * As soon as the server is listening, a "listen" message is emitted.
 *
 * Expect one init request (request that contains tunnel config)
 * {
 *   log:            {Object}      // log configuration
 *   sshUser:        {String}      // username on the ssh server
 *   key:            {Object}      // path to private key to authenticate
 *   forward:        {String}      // ssh -L format [bind_address:]port:host:hostport
 *   host:           {String}      // ssh server to connect to
 *   fingerprint:    {String}      // sha256 hash of the host key in base64 or hex
 *   [port]:         {Number}      // ssh port, defaults to 22
 *   [name]:         {String}      // custom name for this tunnel
 *   [chroot]:       {String}      // defaults to /var/empty
 *   [user]:         {String}      // defaults to "_pdbnull"
 *   [group]:        {String}      // defaults to "_pdbnull"
 * }
 */
process.once('message', function(msg) {
  if (typeof msg !== 'object') { throw new TypeError('msg must be an object'); }

  if (typeof msg.log !== 'object') { throw new TypeError('msg.log must be an object'); }
  if (!msg.sshUser || typeof msg.sshUser !== 'string') { throw new TypeError('msg.sshUser must be a string'); }
  if (!msg.key || typeof msg.key !== 'string') { throw new TypeError('msg.key must be a string'); }
  if (!msg.forward || typeof msg.forward !== 'string') { throw new TypeError('msg.forward must be a string'); }
  if (!msg.host || typeof msg.host !== 'string') { throw new TypeError('msg.host must be a string'); }
  if (!msg.fingerprint || typeof msg.fingerprint !== 'string') { throw new TypeError('msg.fingerprint must be a string'); }

  if (msg.port != null && typeof msg.port !== 'number') { throw new TypeError('msg.port must be a number'); }
  if (msg.name != null && typeof msg.name !== 'string') { throw new TypeError('msg.name must be a string'); }
  if (msg.chroot != null && typeof msg.chroot !== 'string') { throw new TypeError('msg.chroot must be a string'); }
  if (msg.user != null && typeof msg.user !== 'string') { throw new TypeError('msg.user must be a string'); }
  if (msg.group != null && typeof msg.group !== 'string') { throw new TypeError('msg.group must be a string'); }

  if (msg.fingerprint.length !== 43 && msg.fingerprint.length !== 64) { throw new Error('msg.fingerprint must be a hex or base64 encoded sha256 hash'); }

  programName = 'tunnel ' + msg.forward;
  if (msg.name) {
    programName = 'tunnel ' + msg.name;
  }

  process.title = 'pdb/' + programName;

  var user = msg.user || '_pdbnull';
  var group = msg.group || '_pdbnull';

  var newRoot = msg.chroot || '/var/empty';

  msg.log.ident = programName;

  var forward = msg.forward.split(':');
  if (forward.length < 3 || forward.length > 4) {
    throw new Error('msg.forward format must follow [bind_address:]port:host:hostport');
  }

  // open log
  logger(msg.log, function(err, l) {
    if (err) { l.err(err); throw err; }

    log = l;

    // make sure the key is not world readable/writable
    var stats = fs.statSync(msg.key);
    if ((stats.mode & 0o066) !== 0) {
      log.err('msg.key should not be group or world readable or writable');
      process.exit(12);
    }

    var key = fs.readFileSync(msg.key);

    var uid, gid;
    try {
      uid = posix.getpwnam(user).uid;
      gid = posix.getgrnam(group).gid;
    } catch(err) {
      log.err('tunnel %s %s:%s', err, user, group);
      process.exit(3);
    }

    // create the tunnel or exit
    var tunnel = new ssh.Client();

    // start dns lookup of hostname before chrooting
    dns.lookup(msg.host, function(err, hostIp) {
      if (err) {
        log.err('tunnel could not lookup %s %s', msg.host, err);
        process.exit(4);
      }
      log.notice('resolved %s to %s', msg.host, hostIp);

      // ensure hex and base64 fingerprint
      var expectedFingerprintHex, expectedFingerprintBase64;
      if (msg.fingerprint.length === 64) {
        expectedFingerprintHex = msg.fingerprint;
        expectedFingerprintBase64 = new Buffer(msg.fingerprint, 'hex').toString('base64');
      } else {
        expectedFingerprintBase64 = msg.fingerprint;
        expectedFingerprintHex = new Buffer(msg.fingerprint, 'base64').toString('hex');
      }

      tunnel.connect({
        host: hostIp,
        port: msg.port || 22,
        username: msg.sshUser,
        privateKey: key,
        hostHash: 'sha256',
        debug: log.debug2,
        hostVerifier: function(hostFingerprintHex) {
          var hostFingerprintBase64 = new Buffer(hostFingerprintHex, 'hex').toString('base64');
          if (hostFingerprintHex === expectedFingerprintHex) {
            log.info('host key verified: %s', expectedFingerprintBase64);
            return true;
          } else {
            log.err('host key mismatch! expected %s got %s', expectedFingerprintBase64, hostFingerprintBase64);
            return false;
          }
        }
      });
    });

    // chroot or exit
    try {
      chroot(newRoot, uid, gid);
      log.info('changed root to %s and user:group to %s:%s', newRoot, user, group);
    } catch(err) {
      log.err('changing root or user failed %j %s', msg, err);
      process.exit(8);
    }

    var srcHost = '127.0.0.1';
    if (forward.length === 4) {
      srcHost = forward.shift();
    }

    var dstHost = forward[1];

    var srcPort = parseInt(forward[0]);
    var dstPort = parseInt(forward[2]);
    if (srcPort < 1024) {
      log.err('only source ports >1024 are supported');
      process.exit(5);
    }

    var gchannel, srv;
    tunnel.on('ready', function() {
      log.notice('connected to %s', msg.host);
      srv = net.createServer(function(sock) {
        tunnel.forwardOut(sock.remoteAddress, sock.remotePort, dstHost, dstPort, function(err, channel) {
          if (err) {
            log.err(err);
            sock.end();
          }
          channel.on('close', function() {
            log.notice('tunnel closed');
          });
          gchannel = channel;
          sock.pipe(channel).pipe(sock);
          log.notice('tunnel ready %s:%d:%s:%d', sock.remoteAddress, sock.remotePort, dstHost, dstPort);
        });
      });
      srv.listen(srcPort, srcHost, function() {
        process.send('listen');
      });
    });

    tunnel.on('error', function(err) {
      log.err(err);
    });

    tunnel.on('close', function(hadError) {
      log.notice('closed');
      srv && srv.close();
      if (hadError) {
        log.err('closed with error');
      }
    });

    // handle shutdown
    function shutdown() {
      log.info('closing...');
      gchannel.end();
      tunnel.end();
    }

    // listen to kill signals
    process.once('SIGTERM', shutdown);
    process.once('SIGINT', shutdown);

    process.once('message', shutdown); // expect msg.type == kill
  });
});

process.send('init');
<commit_msg>tunnel_exec: small fixes<commit_after>/**
 * Copyright 2016 Netsend.
 *
 * This file is part of PersDB.
 *
 * PersDB is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Affero General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * PersDB is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with PersDB. If not, see <https://www.gnu.org/licenses/>.
 */

'use strict';

var programName = require('path').basename(__filename, '.js');

if (process.getuid() !== 0) {
  console.error('%s: execute as root', programName);
  process.exit(1);
}

if (typeof process.send !== 'function') {
  console.error('%s: use child_process.spawn to invoke this module', programName);
  process.exit(2);
}

var dns = require('dns');
var fs = require('fs');
var net = require('net');

var chroot = require('chroot');
var posix = require('posix');
var ssh = require('ssh2');

var logger = require('./logger');

var log; // used after receiving the log configuration

/**
 * Instantiate an ssh tunnel.
 *
 * Full FSM: init --> listen
 *
 * The first message emitted is "init" which signals that this process is ready to
 * receive the configuration data. File desciptors for the log config should be
 * sent in subsequent messages.
 *
 * Only a kill message can be sent to signal the end of the process.
 *
 * As soon as the server is listening, a "listen" message is emitted.
 *
 * Expect one init request (request that contains tunnel config)
 * {
 *   log:            {Object}      // log configuration
 *   sshUser:        {String}      // username on the ssh server
 *   key:            {Object}      // path to private key to authenticate
 *   forward:        {String}      // ssh -L format [bind_address:]port:host:hostport
 *   host:           {String}      // ssh server to connect to
 *   fingerprint:    {String}      // sha256 hash of the host key in base64 or hex
 *   [port]:         {Number}      // ssh port, defaults to 22
 *   [name]:         {String}      // custom name for this tunnel
 *   [chroot]:       {String}      // defaults to /var/empty
 *   [user]:         {String}      // defaults to "_pdbnull"
 *   [group]:        {String}      // defaults to "_pdbnull"
 * }
 */
process.once('message', function(msg) {
  if (typeof msg !== 'object') { throw new TypeError('msg must be an object'); }

  if (typeof msg.log !== 'object') { throw new TypeError('msg.log must be an object'); }
  if (!msg.sshUser || typeof msg.sshUser !== 'string') { throw new TypeError('msg.sshUser must be a string'); }
  if (!msg.key || typeof msg.key !== 'string') { throw new TypeError('msg.key must be a string'); }
  if (!msg.forward || typeof msg.forward !== 'string') { throw new TypeError('msg.forward must be a string'); }
  if (!msg.host || typeof msg.host !== 'string') { throw new TypeError('msg.host must be a string'); }
  if (!msg.fingerprint || typeof msg.fingerprint !== 'string') { throw new TypeError('msg.fingerprint must be a string'); }

  if (msg.port != null && typeof msg.port !== 'number') { throw new TypeError('msg.port must be a number'); }
  if (msg.name != null && typeof msg.name !== 'string') { throw new TypeError('msg.name must be a string'); }
  if (msg.chroot != null && typeof msg.chroot !== 'string') { throw new TypeError('msg.chroot must be a string'); }
  if (msg.user != null && typeof msg.user !== 'string') { throw new TypeError('msg.user must be a string'); }
  if (msg.group != null && typeof msg.group !== 'string') { throw new TypeError('msg.group must be a string'); }

  if (msg.fingerprint.length !== 43 && msg.fingerprint.length !== 64) { throw new Error('msg.fingerprint must be a hex or base64 encoded sha256 hash'); }

  programName = 'tunnel ' + msg.forward;
  if (msg.name) {
    programName = 'tunnel ' + msg.name;
  }

  process.title = 'pdb/' + programName;

  var user = msg.user || '_pdbnull';
  var group = msg.group || '_pdbnull';

  var newRoot = msg.chroot || '/var/empty';

  msg.log.ident = programName;

  var forward = msg.forward.split(':');
  if (forward.length < 3 || forward.length > 4) {
    throw new Error('msg.forward format must follow [bind_address:]port:host:hostport');
  }

  // open log
  logger(msg.log, function(err, l) {
    if (err) { l.err(err); throw err; }

    log = l;

    // make sure the key is not world readable/writable
    var stats = fs.statSync(msg.key);
    if ((stats.mode & 0o066) !== 0) {
      log.err('msg.key should not be group or world readable or writable');
      process.exit(12);
    }

    var key = fs.readFileSync(msg.key);

    var uid, gid;
    try {
      uid = posix.getpwnam(user).uid;
      gid = posix.getgrnam(group).gid;
    } catch(err) {
      log.err('tunnel %s %s:%s', err, user, group);
      process.exit(3);
    }

    // create the tunnel or exit
    var tunnel = new ssh.Client();

    // start dns lookup of hostname before chrooting
    dns.lookup(msg.host, function(err, hostIp) {
      if (err) {
        log.err('tunnel could not lookup %s %s', msg.host, err);
        process.exit(4);
      }
      log.notice('resolved %s to %s', msg.host, hostIp);

      // ensure hex and base64 fingerprint
      var expectedFingerprintHex, expectedFingerprintBase64;
      if (msg.fingerprint.length === 64) {
        expectedFingerprintHex = msg.fingerprint;
        expectedFingerprintBase64 = new Buffer(msg.fingerprint, 'hex').toString('base64');
      } else {
        expectedFingerprintBase64 = msg.fingerprint;
        expectedFingerprintHex = new Buffer(msg.fingerprint, 'base64').toString('hex');
      }

      tunnel.connect({
        host: hostIp,
        port: msg.port || 22,
        username: msg.sshUser,
        privateKey: key,
        hostHash: 'sha256',
        debug: log.debug2,
        hostVerifier: function(hostFingerprintHex) {
          var hostFingerprintBase64 = new Buffer(hostFingerprintHex, 'hex').toString('base64');
          if (hostFingerprintHex === expectedFingerprintHex) {
            log.info('host key verified: %s', expectedFingerprintBase64);
            return true;
          } else {
            log.err('host key mismatch! expected %s got %s', expectedFingerprintBase64, hostFingerprintBase64);
            return false;
          }
        }
      });
    });

    // chroot or exit
    try {
      chroot(newRoot, uid, gid);
      log.info('changed root to %s and user:group to %s:%s', newRoot, user, group);
    } catch(err) {
      log.err('changing root or user failed %j %s', msg, err);
      process.exit(8);
    }

    var srcHost = '127.0.0.1';
    if (forward.length === 4) {
      srcHost = forward.shift();
    }

    var dstHost = forward[1];

    var srcPort = parseInt(forward[0]);
    var dstPort = parseInt(forward[2]);
    if (srcPort < 1024) {
      log.err('only source ports >1024 are supported');
      process.exit(5);
    }

    var srv;
    tunnel.on('ready', function() {
      log.notice('tunnel ready %s', msg.host);
      srv = net.createServer(function(sock) {
        tunnel.forwardOut(sock.remoteAddress, sock.remotePort, dstHost, dstPort, function(err, channel) {
          if (err) {
            log.err(err);
            sock.end();
          }
          sock.pipe(channel).pipe(sock);
          log.notice('tunnel ready %s:%d:%s:%d', sock.remoteAddress, sock.remotePort, dstHost, dstPort);
        });
      });
      srv.listen(srcPort, srcHost, function() {
        log.notice('listening on %s:%d', srcHost, srcPort);
        process.send('listen');
      });
      srv.on('close', function() {
        log.notice('closed listening socket %s:%d', srcHost, srcPort);
      });
    });

    tunnel.on('error', function(err) {
      log.err(err);
    });

    tunnel.on('close', function(hadError) {
      log.notice('tunnel closed');
      srv && srv.close();
      if (hadError) {
        log.err('closed with error');
      }
    });

    // handle shutdown
    function shutdown() {
      log.info('closing...');
      tunnel.end();
    }

    // listen to kill signals
    process.once('SIGTERM', shutdown);
    process.once('SIGINT', shutdown);

    process.once('message', shutdown); // expect msg.type == kill
  });
});

process.send('init');
<|endoftext|>