<commit_before>/*!
 * jQuery UI Widget @VERSION
 *
 * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 *
 * http://docs.jquery.com/UI/Widget
 */
(function( $, undefined ) {

var slice = Array.prototype.slice;

var _cleanData = $.cleanData;
$.cleanData = function( elems ) {
	for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
		$( elem ).triggerHandler( "remove" );
	}
	_cleanData( elems );
};

$.widget = function( name, base, prototype ) {
	var namespace = name.split( "." )[ 0 ],
		fullName;
	name = name.split( "." )[ 1 ];
	fullName = namespace + "-" + name;

	if ( !prototype ) {
		prototype = base;
		base = $.Widget;
	}

	// create selector for plugin
	$.expr[ ":" ][ fullName ] = function( elem ) {
		return !!$.data( elem, name );
	};

	$[ namespace ] = $[ namespace ] || {};
	// create the constructor using $.extend() so we can carry over any
	// static properties stored on the existing constructor (if there is one)
	$[ namespace ][ name ] = $.extend( function( options, element ) {
		// allow instantiation without "new" keyword
		if ( !this._createWidget ) {
			return new $[ namespace ][ name ]( options, element );
		}

		// allow instantiation without initializing for simple inheritance
		// must use "new" keyword (the code above always passes args)
		if ( arguments.length ) {
			this._createWidget( options, element );
		}
	}, $[ namespace ][ name ] );

	var basePrototype = new base();
	// we need to make the options hash a property directly on the new instance
	// otherwise we'll modify the options hash on the prototype that we're
	// inheriting from
	basePrototype.options = $.extend( true, {}, basePrototype.options );
	$.each( prototype, function( prop, value ) {
		if ( $.isFunction( value ) ) {
			prototype[ prop ] = (function() {
				var _super = function( method ) {
					return base.prototype[ method ].apply( this, slice.call( arguments, 1 ) );
				};
				var _superApply = function( method, args ) {
					return base.prototype[ method ].apply( this, args );
				};
				return function() {
					var __super = this._super,
						__superApply = this._superApply,
						returnValue;

					this._super = _super;
					this._superApply = _superApply;

					returnValue = value.apply( this, arguments );

					this._super = __super;
					this._superApply = __superApply;

					return returnValue;
				};
			}());
		}
	});
	$[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
		namespace: namespace,
		widgetName: name,
		widgetEventPrefix: name,
		widgetBaseClass: fullName
	}, prototype );

	$.widget.bridge( name, $[ namespace ][ name ] );
};

$.widget.bridge = function( name, object ) {
	$.fn[ name ] = function( options ) {
		var isMethodCall = typeof options === "string",
			args = slice.call( arguments, 1 ),
			returnValue = this;

		// allow multiple hashes to be passed on init
		options = !isMethodCall && args.length ?
			$.extend.apply( null, [ true, options ].concat(args) ) :
			options;

		if ( isMethodCall ) {
			this.each(function() {
				var instance = $.data( this, name );
				if ( !instance ) {
					return $.error( "cannot call methods on " + name + " prior to initialization; " +
						"attempted to call method '" + options + "'" );
				}
				if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
					return $.error( "no such method '" + options + "' for " + name + " widget instance" );
				}
				var methodValue = instance[ options ].apply( instance, args );
				if ( methodValue !== instance && methodValue !== undefined ) {
					returnValue = methodValue;
					return false;
				}
			});
		} else {
			this.each(function() {
				var instance = $.data( this, name );
				if ( instance ) {
					instance.option( options || {} )._init();
				} else {
					object( options, this );
				}
			});
		}

		return returnValue;
	};
};

$.Widget = function( options, element ) {
	// allow instantiation without "new" keyword
	if ( !this._createWidget ) {
		return new $[ namespace ][ name ]( options, element );
	}

	// allow instantiation without initializing for simple inheritance
	// must use "new" keyword (the code above always passes args)
	if ( arguments.length ) {
		this._createWidget( options, element );
	}
};

$.Widget.prototype = {
	widgetName: "widget",
	widgetEventPrefix: "",
	defaultElement: "<div>",
	options: {
		disabled: false
	},
	_createWidget: function( options, element ) {
		element = $( element || this.defaultElement || this )[ 0 ];
		this.element = $( element );
		this.options = $.extend( true, {},
			this.options,
			this._getCreateOptions(),
			options );

		this.bindings = $();
		this.hoverable = $();
		this.focusable = $();

		if ( element !== this ) {
			$.data( element, this.widgetName, this );
			this._bind({ remove: "destroy" });
		}

		this._create();
		this._trigger( "create" );
		this._init();
	},
	_getCreateOptions: function() {
		return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
	},
	_create: $.noop,
	_init: $.noop,

	destroy: function() {
		this._destroy();
		// we can probably remove the unbind calls in 2.0
		// all event bindings should go through this._bind()
		this.element
			.unbind( "." + this.widgetName )
			.removeData( this.widgetName );
		this.widget()
			.unbind( "." + this.widgetName )
			.removeAttr( "aria-disabled" )
			.removeClass(
				this.widgetBaseClass + "-disabled " +
				"ui-state-disabled" );

		// clean up events and states
		this.bindings.unbind( "." + this.widgetName );
		this.hoverable.removeClass( "ui-state-hover" );
		this.focusable.removeClass( "ui-state-focus" );
	},
	_destroy: $.noop,

	widget: function() {
		return this.element;
	},

	option: function( key, value ) {
		var options = key;

		if ( arguments.length === 0 ) {
			// don't return a reference to the internal hash
			return $.extend( {}, this.options );
		}

		if  (typeof key === "string" ) {
			if ( value === undefined ) {
				return this.options[ key ];
			}
			options = {};
			options[ key ] = value;
		}

		this._setOptions( options );

		return this;
	},
	_setOptions: function( options ) {
		var self = this;
		$.each( options, function( key, value ) {
			self._setOption( key, value );
		});

		return this;
	},
	_setOption: function( key, value ) {
		this.options[ key ] = value;

		if ( key === "disabled" ) {
			this.widget()
				.toggleClass( this.widgetBaseClass + "-disabled ui-state-disabled", !!value )
				.attr( "aria-disabled", value );
			this.hoverable.removeClass( "ui-state-hover" );
			this.focusable.removeClass( "ui-state-focus" );
		}

		return this;
	},

	enable: function() {
		return this._setOption( "disabled", false );
	},
	disable: function() {
		return this._setOption( "disabled", true );
	},

	_bind: function( element, handlers ) {
		// no element argument, shuffle and use this.element
		if ( !handlers ) {
			handlers = element;
			element = this.element;
		} else {
			this.bindings = this.bindings.add( element );
		}
		var instance = this;
		$.each( handlers, function( event, handler ) {
			element.bind( event + "." + instance.widgetName, function() {
				// allow widgets to customize the disabled handling
				// - disabled as an array instead of boolean
				// - disabled class as method for disabling individual parts
				if ( instance.options.disabled === true ||
						$( this ).hasClass( "ui-state-disabled" ) ) {
					return;
				}
				return ( typeof handler === "string" ? instance[ handler ] : handler )
					.apply( instance, arguments );
			});
		});
	},

	_hoverable: function( element ) {
		this.hoverable = this.hoverable.add( element );
		this._bind( element, {
			mouseenter: function( event ) {
				$( event.currentTarget ).addClass( "ui-state-hover" );
			},
			mouseleave: function( event ) {
				$( event.currentTarget ).removeClass( "ui-state-hover" );
			}
		});
	},

	_focusable: function( element ) {
		this.focusable = this.focusable.add( element );
		this._bind( element, {
			focusin: function( event ) {
				$( event.currentTarget ).addClass( "ui-state-focus" );
			},
			focusout: function( event ) {
				$( event.currentTarget ).removeClass( "ui-state-focus" );
			}
		});
	},

	_trigger: function( type, event, data ) {
		var callback = this.options[ type ],
			args;

		event = $.Event( event );
		event.type = ( type === this.widgetEventPrefix ?
			type :
			this.widgetEventPrefix + type ).toLowerCase();
		data = data || {};

		// copy original event properties over to the new event
		// this would happen if we could call $.event.fix instead of $.Event
		// but we don't have a way to force an event to be fixed multiple times
		if ( event.originalEvent ) {
			for ( var i = $.event.props.length, prop; i; ) {
				prop = $.event.props[ --i ];
				event[ prop ] = event.originalEvent[ prop ];
			}
		}

		this.element.trigger( event, data );

		args = $.isArray( data ) ?
			[ event ].concat( data ) :
			[ event, data ];

		return !( $.isFunction( callback ) &&
			callback.apply( this.element[0], args ) === false ||
			event.isDefaultPrevented() );
	}
};

})( jQuery );
<commit_msg>Widget: Accept selectors and DOM elements as element-argument to _bind<commit_after>/*!
 * jQuery UI Widget @VERSION
 *
 * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 *
 * http://docs.jquery.com/UI/Widget
 */
(function( $, undefined ) {

var slice = Array.prototype.slice;

var _cleanData = $.cleanData;
$.cleanData = function( elems ) {
	for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
		$( elem ).triggerHandler( "remove" );
	}
	_cleanData( elems );
};

$.widget = function( name, base, prototype ) {
	var namespace = name.split( "." )[ 0 ],
		fullName;
	name = name.split( "." )[ 1 ];
	fullName = namespace + "-" + name;

	if ( !prototype ) {
		prototype = base;
		base = $.Widget;
	}

	// create selector for plugin
	$.expr[ ":" ][ fullName ] = function( elem ) {
		return !!$.data( elem, name );
	};

	$[ namespace ] = $[ namespace ] || {};
	// create the constructor using $.extend() so we can carry over any
	// static properties stored on the existing constructor (if there is one)
	$[ namespace ][ name ] = $.extend( function( options, element ) {
		// allow instantiation without "new" keyword
		if ( !this._createWidget ) {
			return new $[ namespace ][ name ]( options, element );
		}

		// allow instantiation without initializing for simple inheritance
		// must use "new" keyword (the code above always passes args)
		if ( arguments.length ) {
			this._createWidget( options, element );
		}
	}, $[ namespace ][ name ] );

	var basePrototype = new base();
	// we need to make the options hash a property directly on the new instance
	// otherwise we'll modify the options hash on the prototype that we're
	// inheriting from
	basePrototype.options = $.extend( true, {}, basePrototype.options );
	$.each( prototype, function( prop, value ) {
		if ( $.isFunction( value ) ) {
			prototype[ prop ] = (function() {
				var _super = function( method ) {
					return base.prototype[ method ].apply( this, slice.call( arguments, 1 ) );
				};
				var _superApply = function( method, args ) {
					return base.prototype[ method ].apply( this, args );
				};
				return function() {
					var __super = this._super,
						__superApply = this._superApply,
						returnValue;

					this._super = _super;
					this._superApply = _superApply;

					returnValue = value.apply( this, arguments );

					this._super = __super;
					this._superApply = __superApply;

					return returnValue;
				};
			}());
		}
	});
	$[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
		namespace: namespace,
		widgetName: name,
		widgetEventPrefix: name,
		widgetBaseClass: fullName
	}, prototype );

	$.widget.bridge( name, $[ namespace ][ name ] );
};

$.widget.bridge = function( name, object ) {
	$.fn[ name ] = function( options ) {
		var isMethodCall = typeof options === "string",
			args = slice.call( arguments, 1 ),
			returnValue = this;

		// allow multiple hashes to be passed on init
		options = !isMethodCall && args.length ?
			$.extend.apply( null, [ true, options ].concat(args) ) :
			options;

		if ( isMethodCall ) {
			this.each(function() {
				var instance = $.data( this, name );
				if ( !instance ) {
					return $.error( "cannot call methods on " + name + " prior to initialization; " +
						"attempted to call method '" + options + "'" );
				}
				if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
					return $.error( "no such method '" + options + "' for " + name + " widget instance" );
				}
				var methodValue = instance[ options ].apply( instance, args );
				if ( methodValue !== instance && methodValue !== undefined ) {
					returnValue = methodValue;
					return false;
				}
			});
		} else {
			this.each(function() {
				var instance = $.data( this, name );
				if ( instance ) {
					instance.option( options || {} )._init();
				} else {
					object( options, this );
				}
			});
		}

		return returnValue;
	};
};

$.Widget = function( options, element ) {
	// allow instantiation without "new" keyword
	if ( !this._createWidget ) {
		return new $[ namespace ][ name ]( options, element );
	}

	// allow instantiation without initializing for simple inheritance
	// must use "new" keyword (the code above always passes args)
	if ( arguments.length ) {
		this._createWidget( options, element );
	}
};

$.Widget.prototype = {
	widgetName: "widget",
	widgetEventPrefix: "",
	defaultElement: "<div>",
	options: {
		disabled: false
	},
	_createWidget: function( options, element ) {
		element = $( element || this.defaultElement || this )[ 0 ];
		this.element = $( element );
		this.options = $.extend( true, {},
			this.options,
			this._getCreateOptions(),
			options );

		this.bindings = $();
		this.hoverable = $();
		this.focusable = $();

		if ( element !== this ) {
			$.data( element, this.widgetName, this );
			this._bind({ remove: "destroy" });
		}

		this._create();
		this._trigger( "create" );
		this._init();
	},
	_getCreateOptions: function() {
		return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
	},
	_create: $.noop,
	_init: $.noop,

	destroy: function() {
		this._destroy();
		// we can probably remove the unbind calls in 2.0
		// all event bindings should go through this._bind()
		this.element
			.unbind( "." + this.widgetName )
			.removeData( this.widgetName );
		this.widget()
			.unbind( "." + this.widgetName )
			.removeAttr( "aria-disabled" )
			.removeClass(
				this.widgetBaseClass + "-disabled " +
				"ui-state-disabled" );

		// clean up events and states
		this.bindings.unbind( "." + this.widgetName );
		this.hoverable.removeClass( "ui-state-hover" );
		this.focusable.removeClass( "ui-state-focus" );
	},
	_destroy: $.noop,

	widget: function() {
		return this.element;
	},

	option: function( key, value ) {
		var options = key;

		if ( arguments.length === 0 ) {
			// don't return a reference to the internal hash
			return $.extend( {}, this.options );
		}

		if  (typeof key === "string" ) {
			if ( value === undefined ) {
				return this.options[ key ];
			}
			options = {};
			options[ key ] = value;
		}

		this._setOptions( options );

		return this;
	},
	_setOptions: function( options ) {
		var self = this;
		$.each( options, function( key, value ) {
			self._setOption( key, value );
		});

		return this;
	},
	_setOption: function( key, value ) {
		this.options[ key ] = value;

		if ( key === "disabled" ) {
			this.widget()
				.toggleClass( this.widgetBaseClass + "-disabled ui-state-disabled", !!value )
				.attr( "aria-disabled", value );
			this.hoverable.removeClass( "ui-state-hover" );
			this.focusable.removeClass( "ui-state-focus" );
		}

		return this;
	},

	enable: function() {
		return this._setOption( "disabled", false );
	},
	disable: function() {
		return this._setOption( "disabled", true );
	},

	_bind: function( element, handlers ) {
		// no element argument, shuffle and use this.element
		if ( !handlers ) {
			handlers = element;
			element = this.element;
		} else {
			// accept selectors, DOM elements
			element = $( element );
			this.bindings = this.bindings.add( element );
		}
		var instance = this;
		$.each( handlers, function( event, handler ) {
			element.bind( event + "." + instance.widgetName, function() {
				// allow widgets to customize the disabled handling
				// - disabled as an array instead of boolean
				// - disabled class as method for disabling individual parts
				if ( instance.options.disabled === true ||
						$( this ).hasClass( "ui-state-disabled" ) ) {
					return;
				}
				return ( typeof handler === "string" ? instance[ handler ] : handler )
					.apply( instance, arguments );
			});
		});
	},

	_hoverable: function( element ) {
		this.hoverable = this.hoverable.add( element );
		this._bind( element, {
			mouseenter: function( event ) {
				$( event.currentTarget ).addClass( "ui-state-hover" );
			},
			mouseleave: function( event ) {
				$( event.currentTarget ).removeClass( "ui-state-hover" );
			}
		});
	},

	_focusable: function( element ) {
		this.focusable = this.focusable.add( element );
		this._bind( element, {
			focusin: function( event ) {
				$( event.currentTarget ).addClass( "ui-state-focus" );
			},
			focusout: function( event ) {
				$( event.currentTarget ).removeClass( "ui-state-focus" );
			}
		});
	},

	_trigger: function( type, event, data ) {
		var callback = this.options[ type ],
			args;

		event = $.Event( event );
		event.type = ( type === this.widgetEventPrefix ?
			type :
			this.widgetEventPrefix + type ).toLowerCase();
		data = data || {};

		// copy original event properties over to the new event
		// this would happen if we could call $.event.fix instead of $.Event
		// but we don't have a way to force an event to be fixed multiple times
		if ( event.originalEvent ) {
			for ( var i = $.event.props.length, prop; i; ) {
				prop = $.event.props[ --i ];
				event[ prop ] = event.originalEvent[ prop ];
			}
		}

		this.element.trigger( event, data );

		args = $.isArray( data ) ?
			[ event ].concat( data ) :
			[ event, data ];

		return !( $.isFunction( callback ) &&
			callback.apply( this.element[0], args ) === false ||
			event.isDefaultPrevented() );
	}
};

})( jQuery );
<|endoftext|>


<commit_before>const {join} = require('path');
const expect = require('expect.js');
const simple = require('simple-mock');

const {exec} = require('../utils');
const DevicesManager = require('../../src/application/devicesManager');

const testFsDir = 'testfs';
const testFsPath = join(process.cwd(), testFsDir);
const storageDirName = '.sloth-storage';

describe('devicesManager', () => {
  describe('Constructor', () => {
    it('should throw an error if "devicePath" is undefined', (done) => {
      try {
        const devicesManager = new DevicesManager();
        done(`No error was thrown: "${devicesManager.getDevicesPath()}"`);
      } catch (e) {
        expect(e).to.be.an(Error);
        expect(e.message).to.contain('devicesPath');
        done();
      }
    });
  });

  describe('Initialization', () => {
    let devicesManager;

    beforeEach(() => {
      exec(`mkdir -p ./${testFsDir}/dev{1,2}`);
      exec(`mkdir -p ./${testFsDir}/dev1/${storageDirName}`);
      exec(`mkdir -p ./${testFsDir}/dev2/${storageDirName}`);
    });

    afterEach(() => {
      devicesManager.destroy();
      devicesManager = null;
      exec(`rm -rf ./${testFsDir}`);
    });

    it('should emit "deviceAdded" event', (done) => {
      const lookupDevicesInterval = 100;
      devicesManager = new DevicesManager({devicesPath: testFsPath, lookupDevicesInterval, storageDirName});
      devicesManager.on(DevicesManager.EVENTS.READY, () => {
        expect(devicesManager.getDevices()).to.be.an('array');
        expect(devicesManager.getDevices()).to.have.length(2);

        devicesManager.on(DevicesManager.EVENTS.DEVICE_ADDED, (path) => {
          expect(path).to.be(join(testFsPath, 'dev3', storageDirName));
          expect(devicesManager.getDevices()).to.be.an('array');
          expect(devicesManager.getDevices()).to.have.length(3);
          done();
        });

        exec(`mkdir -p ./${testFsDir}/dev3`);
      });
    });

    it('should emit "deviceRemoved" event', (done) => {
      const lookupDevicesInterval = 100;
      devicesManager = new DevicesManager({devicesPath: testFsPath, lookupDevicesInterval, storageDirName});
      devicesManager.on(DevicesManager.EVENTS.READY, () => {
        expect(devicesManager.getDevices()).to.be.an('array');
        expect(devicesManager.getDevices()).to.have.length(2);

        devicesManager.on(DevicesManager.EVENTS.DEVICE_REMOVED, (path) => {
          expect(path).to.be(join(testFsPath, 'dev1', storageDirName));
          expect(devicesManager.getDevices()).to.be.an('array');
          expect(devicesManager.getDevices()).to.have.length(1);
          done();
        });

        exec(`rm -r ./${testFsDir}/dev1`);
      });
    });

    it('should find new device', (done) => {
      const lookupDevicesInterval = 100;
      devicesManager = new DevicesManager({devicesPath: testFsPath, lookupDevicesInterval, storageDirName});
      devicesManager.on(DevicesManager.EVENTS.READY, () => {
        expect(devicesManager.getDevices()).to.be.an('array');
        expect(devicesManager.getDevices()).to.have.length(2);
        expect(devicesManager.getDevices()).to.contain(join(testFsPath, 'dev1', storageDirName));
        expect(devicesManager.getDevices()).to.contain(join(testFsPath, 'dev2', storageDirName));

        exec(`mkdir -p ./${testFsDir}/dev3`);

        setTimeout(() => {
          expect(devicesManager.getDevices()).to.be.an('array');
          expect(devicesManager.getDevices()).to.have.length(3);
          expect(devicesManager.getDevices()).to.contain(join(testFsPath, 'dev3', storageDirName));
          done();
        }, lookupDevicesInterval * 1.1);
      });
    });

    it('should emit "warning" event if failed to create storage directory on a devices', (done) => {
      const mockFs = require('fs'); //eslint-disable-line global-require
      const lookupDevicesInterval = 1000;
      simple.mock(mockFs, 'mkdir').callbackWith('lol-warn');
      exec(`rm -rf ./${testFsDir}/dev1/${storageDirName}`);
      const stoplookupDevicesInterval = setTimeout(() => {
        simple.restore();
        done('"warning" event hasn\'t been thrown');
      }, lookupDevicesInterval * 0.9);
      devicesManager = new DevicesManager({
        devicesPath: testFsPath,
        lookupDevicesInterval,
        storageDirName,
        fs: mockFs
      });
      devicesManager.on(DevicesManager.EVENTS.WARN, (err) => {
        clearTimeout(stoplookupDevicesInterval);
        expect(err).to.contain('Fail to create storage directory');
        expect(err).to.contain(`${testFsDir}/dev1`);
        expect(err).to.contain('lol-warn');
        simple.restore();
        done();
      });
    });

    it('should emit "warn" event if there are no directories in devices path', (done) => {
      exec(`mkdir -p ./${testFsDir}/empty`);
      const lookupDevicesInterval = 1000;
      const emptyPath = join(testFsPath, 'empty');
      const stoplookupDevicesInterval = setTimeout(() => {
        done('"warn" event hasn\'t been thrown');
      }, lookupDevicesInterval * 0.9);
      devicesManager = new DevicesManager({devicesPath: emptyPath, lookupDevicesInterval, storageDirName});
      devicesManager.on(DevicesManager.EVENTS.WARN, (message) => {
        clearTimeout(stoplookupDevicesInterval);
        expect(message).to.contain('fail to find any devices');
        expect(message).to.contain(emptyPath);
        done();
      });
    });

    it('should emit "error" event for non-existing devices path', (done) => {
      const lookupDevicesInterval = 100;
      const nonExistingPath = '/not-exist';
      const stoplookupDevicesInterval = setTimeout(() => {
        done('"error" event hasn\'t been thrown');
      }, lookupDevicesInterval * 0.9);
      devicesManager = new DevicesManager({devicesPath: nonExistingPath, lookupDevicesInterval, storageDirName});
      devicesManager.on(DevicesManager.EVENTS.ERROR, (e) => {
        clearTimeout(stoplookupDevicesInterval);
        expect(e).to.be.an(Error);
        expect(e.message).to.contain('Cannot read devices directory');
        expect(e.message).to.contain('ENOENT');
        expect(e.message).to.contain(nonExistingPath);
        done();
      });
    });
  });

  describe('Runtime methods', () => {
    let devicesManager;
    const lookupDevicesInterval = 100;
    const calculateCapacityInterval = 100;

    beforeEach((done) => {
      exec(`mkdir -p ./${testFsDir}/dev{1,2}`);

      devicesManager = new DevicesManager({
        devicesPath: testFsPath,
        lookupDevicesInterval,
        calculateCapacityInterval,
        storageDirName
      });
      //devicesManager.on(DevicesManager.EVENTS.WARN, message => console.log(`WARN: ${message}`));
      //devicesManager.on(DevicesManager.EVENTS.ERROR, message => console.log(`ERROR: ${message}`));
      devicesManager.on(DevicesManager.EVENTS.READY, () => done());
    });

    afterEach(() => {
      devicesManager.destroy();
      devicesManager = null;
      exec(`rm -rf ./${testFsDir}`);
    });

    describe('#getDevicesPath()', () => {
      it('should return devices path', () => {
        const devicesPath = devicesManager.getDevicesPath();
        expect(devicesPath).to.be.a('string');
        expect(devicesPath).to.be(testFsPath);
      });
    });

    describe('#getDevices()', () => {
      it('should return devices list', () => {
        const devices = devicesManager.getDevices();
        expect(devices).to.be.an('array');
        expect(devices).to.have.length(2);
        expect(devices).to.contain(join(testFsPath, 'dev1', storageDirName));
        expect(devices).to.contain(join(testFsPath, 'dev2', storageDirName));
      });

      it('should return ordered devices list', () => {
        const devices = devicesManager.getDevices(true);
        expect(devices).to.be.an('array');
        expect(devices).to.have.length(2);
        expect(devices[0]).to.be(join(testFsPath, 'dev1', storageDirName));
        expect(devices[1]).to.be(join(testFsPath, 'dev2', storageDirName));
      });
    });

    describe('#getDeviceForWrite()', () => {
      it('should return device from list', (done) => {
        const devices = devicesManager.getDevices();

        devicesManager.getDeviceForWrite((err, devicesForWrite) => {
          expect(devices).to.be.an('array');
          devicesForWrite.forEach((device) => {
            expect(devices).to.contain(device);
          });
          done(err);
        });
      });

      it('should return an error if no devices exist', (done) => {
        exec(`rm -rf ./${testFsDir}/*`);

        setTimeout(() => {
          devicesManager.getDeviceForWrite((err) => {
            expect(err.message).to.contain('No devices for write');
            done();
          });
        }, lookupDevicesInterval * 1.1);
      });
    });

    describe('#getDeviceForWriteSync()', () => {
      it('should return device from list', (done) => {
        const devices = devicesManager.getDevices();
        try {
          expect(devices).to.be.an('array');
          devicesManager.getDeviceForWriteSync().forEach((device) => {
            expect(devices).to.contain(device);
          });
          done();
        } catch (e) {
          done(e);
        }
      });

      it('should throw an error return null if no devices exist', (done) => {
        exec(`rm -rf ./${testFsDir}/*`);

        setTimeout(() => {
          expect(devicesManager.getDeviceForWriteSync.bind(devicesManager)).to.throwException((err) => {
            expect(err).to.be.a(Error);
            expect(err.message).to.contain('No devices for write');
            done();
          });
        }, lookupDevicesInterval * 1.1);
      });
    });

    describe('#_mapDevicesNames()', () => {
      it('should return device names only', () => {
        const deviceNames = devicesManager._mapDevicesNames([`/${testFsDir}/dev1/${storageDirName}`]);
        expect(deviceNames).to.be.an('array');
        expect(deviceNames).to.have.length(1);
        expect(deviceNames[0]).to.be('dev1');
      });
    });

    describe('#_parseDfOutput()', () => {
      it('should return device stats, totalCapacity and usedCapacityPercent', () => {
        const dev1 = `${devicesManager.getDevicesPath()}/dev1`;
        const dev1Key = `${dev1}/${storageDirName}`;
        const dev2 = `${devicesManager.getDevicesPath()}/dev2`;
        const dev2Key = `${dev2}/${storageDirName}`;
        const df = devicesManager._parseDfOutput(
          [
            'Mounted on   1K-blocks Use%',
            '/dev           9999999   0%',
            '/run           9999999   1%',
            '/              9999999  10%',
            `${dev1}            100  50%`,
            `${dev2}            200  50%`
          ].join('\n')
        );
        expect(df).to.be.an('object');
        expect(df).to.only.have.keys('stats', 'totalCapacity', 'usedCapacityPercent');
        expect(df.stats).to.be.an('object');
        expect(df.stats).to.only.have.keys(dev1Key, dev2Key);
        expect(df.stats[dev1Key]).to.be.an('object');
        expect(df.stats[dev1Key]).to.only.have.keys('usedPercent', 'size');
        expect(df.stats[dev1Key].usedPercent).to.be(0.5);
        expect(df.stats[dev1Key].size).to.be(100 * 1024);
        expect(df.stats[dev2Key]).to.be.an('object');
        expect(df.stats[dev2Key]).to.only.have.keys('usedPercent', 'size');
        expect(df.stats[dev2Key].usedPercent).to.be(0.5);
        expect(df.stats[dev2Key].size).to.be(200 * 1024);
        expect(df.totalCapacity).to.be(300 * 1024);
        expect(df.usedCapacityPercent).to.be(0.5);
      });

      it('should return device stats, totalCapacity and usedCapacityPercent as null if no devices found', () => {
        const df = devicesManager._parseDfOutput('');
        expect(df).to.be.an('object');
        expect(df).to.only.have.keys('stats', 'totalCapacity', 'usedCapacityPercent');
        expect(df.stats).to.be.an('object');
        expect(df.stats).to.be.empty();
        expect(df.totalCapacity).to.be(null);
        expect(df.usedCapacityPercent).to.be(null);
      });
    });

    describe('#_calculateCapacity()', () => {
      let _calculateCapacityFn;

      beforeEach(() => {
        _calculateCapacityFn = simple.mock(devicesManager, '_calculateCapacity');
      });

      afterEach(() => {
        _calculateCapacityFn = null;
        simple.restore();
      });

      it('##getTotalCapacity() should be null by default and after first check', (done) => {
        expect(devicesManager.getTotalCapacity()).to.be(null);

        setTimeout(() => {
          expect(devicesManager.getTotalCapacity()).to.be(null);
          expect(_calculateCapacityFn.callCount).to.be.greaterThan(0);
          done();
        }, calculateCapacityInterval * 1.1);
      });

      it('##getUsedCapacityPercent() should be null by default and after first check', (done) => {
        expect(devicesManager.getUsedCapacityPercent()).to.be(null);

        setTimeout(() => {
          expect(devicesManager.getUsedCapacityPercent()).to.be(null);
          expect(_calculateCapacityFn.callCount).to.be.greaterThan(0);
          done();
        }, calculateCapacityInterval * 1.1);
      });

      it('##getCapacityStats() should be empty by default and after first check', (done) => {
        const stats = devicesManager.getCapacityStats();
        expect(stats).to.be.an('object');
        expect(stats).to.be.empty();

        setTimeout(() => {
          const stats = devicesManager.getCapacityStats();
          expect(stats).to.be.an('object');
          expect(stats).to.be.empty();
          expect(_calculateCapacityFn.callCount).to.be.greaterThan(0);
          done();
        }, calculateCapacityInterval * 1.1);
      });

      it("should emit an WARN event if cannot run 'df' bash command", (done) => {
        const TEST_ERROR = 'TEST_ERROR';
        const localDevicesManager = new DevicesManager({
          devicesPath: testFsPath,
          lookupDevicesInterval,
          calculateCapacityInterval,
          storageDirName,
          childProcess: {exec: (cmd, callback) => callback(TEST_ERROR)}
        });

        localDevicesManager.on(DevicesManager.EVENTS.WARN, (message) => {
          localDevicesManager.destroy();
          expect(message).contain('Fail to get device capacities');
          expect(message).contain(TEST_ERROR);
          done();
        });
      });

      it('should emit an USED_CAPACITY_PERCENT_CHANGED event if devices utilization has been changed', (done) => {
        const dev1 = `${devicesManager.getDevicesPath()}/dev1`;

        const dfOutput1 = [
          'Mounted on   1K-blocks Use%',
          '/              9999999  10%',
          `${dev1}            100  10%`
        ].join('\n');

        const dfOutput2 = [
          'Mounted on   1K-blocks Use%',
          '/              9999999  10%',
          `${dev1}            200  25%`
        ].join('\n');

        const execMock = simple
          .stub()
          .callbackWith(null, dfOutput1)
          .callbackWith(null, dfOutput2);

        const localDevicesManager = new DevicesManager({
          devicesPath: testFsPath,
          lookupDevicesInterval,
          calculateCapacityInterval,
          storageDirName,
          childProcess: {exec: execMock}
        });

        localDevicesManager.on(DevicesManager.EVENTS.USED_CAPACITY_PERCENT_CHANGED, (value) => {
          localDevicesManager.destroy();
          expect(value).to.be(0.25);
          done();
        });
      });
    });
  });
});
<commit_msg>fix: tests racing conditions<commit_after>const {join} = require('path');
const expect = require('expect.js');
const simple = require('simple-mock');

const {exec} = require('../utils');
const DevicesManager = require('../../src/application/devicesManager');

const testFsDir = 'testfs';
const testFsPath = join(process.cwd(), testFsDir);
const storageDirName = '.sloth-storage';

describe('devicesManager', () => {
  describe('Constructor', () => {
    it('should throw an error if "devicePath" is undefined', (done) => {
      try {
        const devicesManager = new DevicesManager();
        done(`No error was thrown: "${devicesManager.getDevicesPath()}"`);
      } catch (e) {
        expect(e).to.be.an(Error);
        expect(e.message).to.contain('devicesPath');
        done();
      }
    });
  });

  describe('Initialization', () => {
    let devicesManager;

    beforeEach(() => {
      exec(`mkdir -p ./${testFsDir}/dev{1,2}`);
      exec(`mkdir -p ./${testFsDir}/dev1/${storageDirName}`);
      exec(`mkdir -p ./${testFsDir}/dev2/${storageDirName}`);
    });

    afterEach(() => {
      devicesManager.destroy();
      devicesManager = null;
      exec(`rm -rf ./${testFsDir}`);
    });

    it('should emit "deviceAdded" event', (done) => {
      const lookupDevicesInterval = 100;
      devicesManager = new DevicesManager({devicesPath: testFsPath, lookupDevicesInterval, storageDirName});
      devicesManager.on(DevicesManager.EVENTS.READY, () => {
        expect(devicesManager.getDevices()).to.be.an('array');
        expect(devicesManager.getDevices()).to.have.length(2);

        devicesManager.on(DevicesManager.EVENTS.DEVICE_ADDED, (path) => {
          expect(path).to.be(join(testFsPath, 'dev3', storageDirName));
          expect(devicesManager.getDevices()).to.be.an('array');
          expect(devicesManager.getDevices()).to.have.length(3);
          done();
        });

        exec(`mkdir -p ./${testFsDir}/dev3`);
      });
    });

    it('should emit "deviceRemoved" event', (done) => {
      const lookupDevicesInterval = 100;
      devicesManager = new DevicesManager({devicesPath: testFsPath, lookupDevicesInterval, storageDirName});
      devicesManager.on(DevicesManager.EVENTS.READY, () => {
        expect(devicesManager.getDevices()).to.be.an('array');
        expect(devicesManager.getDevices()).to.have.length(2);

        devicesManager.on(DevicesManager.EVENTS.DEVICE_REMOVED, (path) => {
          expect(path).to.be(join(testFsPath, 'dev1', storageDirName));
          expect(devicesManager.getDevices()).to.be.an('array');
          expect(devicesManager.getDevices()).to.have.length(1);
          done();
        });

        exec(`rm -r ./${testFsDir}/dev1`);
      });
    });

    it('should find new device', (done) => {
      const lookupDevicesInterval = 100;
      devicesManager = new DevicesManager({devicesPath: testFsPath, lookupDevicesInterval, storageDirName});
      devicesManager.on(DevicesManager.EVENTS.READY, () => {
        expect(devicesManager.getDevices()).to.be.an('array');
        expect(devicesManager.getDevices()).to.have.length(2);
        expect(devicesManager.getDevices()).to.contain(join(testFsPath, 'dev1', storageDirName));
        expect(devicesManager.getDevices()).to.contain(join(testFsPath, 'dev2', storageDirName));

        exec(`mkdir -p ./${testFsDir}/dev3`);

        setTimeout(() => {
          expect(devicesManager.getDevices()).to.be.an('array');
          expect(devicesManager.getDevices()).to.have.length(3);
          expect(devicesManager.getDevices()).to.contain(join(testFsPath, 'dev3', storageDirName));
          done();
        }, lookupDevicesInterval * 1.1);
      });
    });

    it('should emit "warning" event if failed to create storage directory on a devices', (done) => {
      const mockFs = require('fs'); //eslint-disable-line global-require
      const lookupDevicesInterval = 1000;
      simple.mock(mockFs, 'mkdir').callbackWith('lol-warn');
      exec(`rm -rf ./${testFsDir}/dev1/${storageDirName}`);
      const stoplookupDevicesInterval = setTimeout(() => {
        simple.restore();
        done('"warning" event hasn\'t been thrown');
      }, lookupDevicesInterval * 0.9);
      devicesManager = new DevicesManager({
        devicesPath: testFsPath,
        lookupDevicesInterval,
        storageDirName,
        fs: mockFs
      });
      devicesManager.on(DevicesManager.EVENTS.WARN, (err) => {
        clearTimeout(stoplookupDevicesInterval);
        expect(err).to.contain('Fail to create storage directory');
        expect(err).to.contain(`${testFsDir}/dev1`);
        expect(err).to.contain('lol-warn');
        simple.restore();
        done();
      });
    });

    it('should emit "warn" event if there are no directories in devices path', (done) => {
      exec(`mkdir -p ./${testFsDir}/empty`);
      const lookupDevicesInterval = 1000;
      const emptyPath = join(testFsPath, 'empty');
      const stoplookupDevicesInterval = setTimeout(() => {
        done('"warn" event hasn\'t been thrown');
      }, lookupDevicesInterval * 0.9);
      devicesManager = new DevicesManager({devicesPath: emptyPath, lookupDevicesInterval, storageDirName});
      devicesManager.on(DevicesManager.EVENTS.WARN, (message) => {
        clearTimeout(stoplookupDevicesInterval);
        expect(message).to.contain('fail to find any devices');
        expect(message).to.contain(emptyPath);
        done();
      });
    });

    it('should emit "error" event for non-existing devices path', (done) => {
      const lookupDevicesInterval = 100;
      const nonExistingPath = '/not-exist';
      const stoplookupDevicesInterval = setTimeout(() => {
        done('"error" event hasn\'t been thrown');
      }, lookupDevicesInterval * 0.9);
      devicesManager = new DevicesManager({devicesPath: nonExistingPath, lookupDevicesInterval, storageDirName});
      devicesManager.on(DevicesManager.EVENTS.ERROR, (e) => {
        clearTimeout(stoplookupDevicesInterval);
        expect(e).to.be.an(Error);
        expect(e.message).to.contain('Cannot read devices directory');
        expect(e.message).to.contain('ENOENT');
        expect(e.message).to.contain(nonExistingPath);
        done();
      });
    });
  });

  describe('Runtime methods', () => {
    let devicesManager;
    const lookupDevicesInterval = 100;
    const calculateCapacityInterval = 100;

    beforeEach((done) => {
      exec(`mkdir -p ./${testFsDir}/dev{1,2}`);

      devicesManager = new DevicesManager({
        devicesPath: testFsPath,
        lookupDevicesInterval,
        calculateCapacityInterval,
        storageDirName
      });
      //devicesManager.on(DevicesManager.EVENTS.WARN, message => console.log(`WARN: ${message}`));
      //devicesManager.on(DevicesManager.EVENTS.ERROR, message => console.log(`ERROR: ${message}`));
      devicesManager.on(DevicesManager.EVENTS.READY, () => done());
    });

    afterEach(() => {
      devicesManager.destroy();
      devicesManager = null;
      exec(`rm -rf ./${testFsDir}`);
    });

    describe('#getDevicesPath()', () => {
      it('should return devices path', () => {
        const devicesPath = devicesManager.getDevicesPath();
        expect(devicesPath).to.be.a('string');
        expect(devicesPath).to.be(testFsPath);
      });
    });

    describe('#getDevices()', () => {
      it('should return devices list', () => {
        const devices = devicesManager.getDevices();
        expect(devices).to.be.an('array');
        expect(devices).to.have.length(2);
        expect(devices).to.contain(join(testFsPath, 'dev1', storageDirName));
        expect(devices).to.contain(join(testFsPath, 'dev2', storageDirName));
      });

      it('should return ordered devices list', () => {
        const devices = devicesManager.getDevices(true);
        expect(devices).to.be.an('array');
        expect(devices).to.have.length(2);
        expect(devices[0]).to.be(join(testFsPath, 'dev1', storageDirName));
        expect(devices[1]).to.be(join(testFsPath, 'dev2', storageDirName));
      });
    });

    describe('#getDeviceForWrite()', () => {
      it('should return device from list', (done) => {
        const devices = devicesManager.getDevices();

        devicesManager.getDeviceForWrite((err, devicesForWrite) => {
          expect(devices).to.be.an('array');
          devicesForWrite.forEach((device) => {
            expect(devices).to.contain(device);
          });
          done(err);
        });
      });

      it('should return an error if no devices exist', (done) => {
        exec(`rm -rf ./${testFsDir}/*`);

        setTimeout(() => {
          devicesManager.getDeviceForWrite((err) => {
            expect(err.message).to.contain('No devices for write');
            done();
          });
        }, lookupDevicesInterval * 1.1);
      });
    });

    describe('#getDeviceForWriteSync()', () => {
      it('should return device from list', (done) => {
        const devices = devicesManager.getDevices();
        try {
          expect(devices).to.be.an('array');
          devicesManager.getDeviceForWriteSync().forEach((device) => {
            expect(devices).to.contain(device);
          });
          done();
        } catch (e) {
          done(e);
        }
      });

      it('should throw an error return null if no devices exist', (done) => {
        exec(`rm -rf ./${testFsDir}/*`);

        setTimeout(() => {
          expect(devicesManager.getDeviceForWriteSync.bind(devicesManager)).to.throwException((err) => {
            expect(err).to.be.a(Error);
            expect(err.message).to.contain('No devices for write');
            done();
          });
        }, lookupDevicesInterval * 1.1);
      });
    });

    describe('#_mapDevicesNames()', () => {
      it('should return device names only', () => {
        const deviceNames = devicesManager._mapDevicesNames([`/${testFsDir}/dev1/${storageDirName}`]);
        expect(deviceNames).to.be.an('array');
        expect(deviceNames).to.have.length(1);
        expect(deviceNames[0]).to.be('dev1');
      });
    });

    describe('#_parseDfOutput()', () => {
      it('should return device stats, totalCapacity and usedCapacityPercent', () => {
        const dev1 = `${devicesManager.getDevicesPath()}/dev1`;
        const dev1Key = `${dev1}/${storageDirName}`;
        const dev2 = `${devicesManager.getDevicesPath()}/dev2`;
        const dev2Key = `${dev2}/${storageDirName}`;
        const df = devicesManager._parseDfOutput(
          [
            'Mounted on   1K-blocks Use%',
            '/dev           9999999   0%',
            '/run           9999999   1%',
            '/              9999999  10%',
            `${dev1}            100  50%`,
            `${dev2}            200  50%`
          ].join('\n')
        );
        expect(df).to.be.an('object');
        expect(df).to.only.have.keys('stats', 'totalCapacity', 'usedCapacityPercent');
        expect(df.stats).to.be.an('object');
        expect(df.stats).to.only.have.keys(dev1Key, dev2Key);
        expect(df.stats[dev1Key]).to.be.an('object');
        expect(df.stats[dev1Key]).to.only.have.keys('usedPercent', 'size');
        expect(df.stats[dev1Key].usedPercent).to.be(0.5);
        expect(df.stats[dev1Key].size).to.be(100 * 1024);
        expect(df.stats[dev2Key]).to.be.an('object');
        expect(df.stats[dev2Key]).to.only.have.keys('usedPercent', 'size');
        expect(df.stats[dev2Key].usedPercent).to.be(0.5);
        expect(df.stats[dev2Key].size).to.be(200 * 1024);
        expect(df.totalCapacity).to.be(300 * 1024);
        expect(df.usedCapacityPercent).to.be(0.5);
      });

      it('should return device stats, totalCapacity and usedCapacityPercent as null if no devices found', () => {
        const df = devicesManager._parseDfOutput('');
        expect(df).to.be.an('object');
        expect(df).to.only.have.keys('stats', 'totalCapacity', 'usedCapacityPercent');
        expect(df.stats).to.be.an('object');
        expect(df.stats).to.be.empty();
        expect(df.totalCapacity).to.be(null);
        expect(df.usedCapacityPercent).to.be(null);
      });
    });

    describe('#_calculateCapacity()', () => {
      let _calculateCapacityFn;

      beforeEach(() => {
        _calculateCapacityFn = simple.mock(devicesManager, '_calculateCapacity');
      });

      afterEach(() => {
        _calculateCapacityFn = null;
        simple.restore();
      });

      it('##getTotalCapacity() should be null by default and after first check', (done) => {
        expect(devicesManager.getTotalCapacity()).to.be(null);

        setTimeout(() => {
          expect(devicesManager.getTotalCapacity()).to.be(null);
          expect(_calculateCapacityFn.callCount).to.be.greaterThan(0);
          done();
        }, calculateCapacityInterval * 1.1);
      });

      it('##getUsedCapacityPercent() should be null by default and after first check', (done) => {
        expect(devicesManager.getUsedCapacityPercent()).to.be(null);

        setTimeout(() => {
          expect(devicesManager.getUsedCapacityPercent()).to.be(null);
          expect(_calculateCapacityFn.callCount).to.be.greaterThan(0);
          done();
        }, calculateCapacityInterval * 1.1);
      });

      it('##getCapacityStats() should be empty by default and after first check', (done) => {
        const stats = devicesManager.getCapacityStats();
        expect(stats).to.be.an('object');
        expect(stats).to.be.empty();

        setTimeout(() => {
          const stats = devicesManager.getCapacityStats();
          expect(stats).to.be.an('object');
          expect(stats).to.be.empty();
          expect(_calculateCapacityFn.callCount).to.be.greaterThan(0);
          done();
        }, calculateCapacityInterval * 1.1);
      });

      it("should emit an WARN event if cannot run 'df' bash command", (done) => {
        const TEST_ERROR = 'TEST_ERROR';
        const localDevicesManager = new DevicesManager({
          devicesPath: testFsPath,
          lookupDevicesInterval: 10000,
          calculateCapacityInterval,
          storageDirName,
          childProcess: {exec: (cmd, callback) => callback(TEST_ERROR)}
        });

        localDevicesManager.on(DevicesManager.EVENTS.WARN, (message) => {
          localDevicesManager.destroy();
          expect(message).contain('Fail to get device capacities');
          expect(message).contain(TEST_ERROR);
          done();
        });
      });

      it('should emit an USED_CAPACITY_PERCENT_CHANGED event if devices utilization has been changed', (done) => {
        const dev1 = `${devicesManager.getDevicesPath()}/dev1`;

        const dfOutput1 = [
          'Mounted on   1K-blocks Use%',
          '/              9999999  10%',
          `${dev1}            100  10%`
        ].join('\n');

        const dfOutput2 = [
          'Mounted on   1K-blocks Use%',
          '/              9999999  10%',
          `${dev1}            200  25%`
        ].join('\n');

        const execMock = simple
          .stub()
          .callbackWith(null, dfOutput1)
          .callbackWith(null, dfOutput2);

        const localDevicesManager = new DevicesManager({
          devicesPath: testFsPath,
          lookupDevicesInterval,
          calculateCapacityInterval,
          storageDirName,
          childProcess: {exec: execMock}
        });

        localDevicesManager.on(DevicesManager.EVENTS.USED_CAPACITY_PERCENT_CHANGED, (value) => {
          localDevicesManager.destroy();
          expect(value).to.be(0.25);
          done();
        });
      });
    });
  });
});
<|endoftext|>


<commit_before>import hedValidator from 'hed-validator'
import path from 'path'
import semver from 'semver'
import utils from '../../utils'
const Issue = utils.issues.Issue

export default function checkHedStrings(events, headers, jsonContents, dir) {
  let issues = []

  // find specified version in sidecar
  const schemaDefinition = {}
  const datasetDescription = jsonContents['/dataset_description.json']

  if (datasetDescription && datasetDescription.HEDVersion) {
    if (semver.valid(datasetDescription.HEDVersion)) {
      schemaDefinition.version = datasetDescription.HEDVersion
    } else {
      schemaDefinition.path = path.join(
        path.resolve(dir),
        'sourcedata',
        datasetDescription.HEDVersion,
      )
    }
  }

  // run HED validator
  return hedValidator.validator
    .buildSchema(schemaDefinition)
    .then(hedSchema => {
      let hedStringsFound = false
      const sidecarIssues = {}
      let sidecarIssuesFound = false
      // loop through event data files
      events.forEach(eventFile => {
        const hedStrings = []
        // get the json sidecar dictionary associated with the event data
        const potentialSidecars = utils.files.potentialLocations(
          eventFile.path.replace('.tsv', '.json'),
        )
        // validate the HED strings in the json sidecars
        for (const sidecarName of potentialSidecars) {
          if (!(sidecarName in sidecarIssues)) {
            const sidecarDictionary = jsonContents[sidecarName]
            let sidecarHedStrings = []
            for (const sidecarKey in sidecarDictionary) {
              const sidecarValue = sidecarDictionary[sidecarKey]
              if (
                sidecarValue !== null &&
                typeof sidecarValue === 'object' &&
                sidecarValue.HED !== undefined
              ) {
                if (typeof sidecarValue.HED === 'string') {
                  sidecarHedStrings.push(sidecarValue.HED)
                } else {
                  sidecarHedStrings = sidecarHedStrings.concat(
                    Object.values(sidecarValue.HED),
                  )
                }
              }
            }
            let fileIssues = []
            for (const hedString of sidecarHedStrings) {
              const [
                isHedStringValid,
                hedIssues,
              ] = hedValidator.validator.validateHedString(
                hedString,
                hedSchema,
                true,
                true,
              )
              if (!isHedStringValid) {
                const convertedIssues = convertHedIssuesToBidsIssues(
                  hedIssues,
                  sidecarName,
                )
                fileIssues = fileIssues.concat(convertedIssues)
                sidecarIssuesFound = true
              }
            }
            sidecarIssues[sidecarName] = fileIssues
            if (fileIssues.length > 0) {
              issues.push(new Issue({ code: 139, file: sidecarName }))
              issues = issues.concat(fileIssues)
            }
          } else if (sidecarIssues[sidecarName].length > 0) {
            sidecarIssuesFound = true
          }
        }
        if (sidecarIssuesFound) {
          return
        }
        const mergedDictionary = utils.files.generateMergedSidecarDict(
          potentialSidecars,
          jsonContents,
        )

        const sidecarHedTags = {}
        for (const sidecarKey in mergedDictionary) {
          const sidecarValue = mergedDictionary[sidecarKey]
          if (
            sidecarValue !== null &&
            typeof sidecarValue === 'object' &&
            sidecarValue.HED !== undefined
          ) {
            const sidecarHedData = sidecarValue.HED
            if (
              typeof sidecarHedData === 'string' &&
              sidecarHedData.split('#').length !== 2
            ) {
              issues.push(
                new Issue({
                  code: 134,
                  file: eventFile.file,
                  evidence: sidecarHedData,
                }),
              )
              sidecarHedTags[sidecarKey] = false
            } else {
              sidecarHedTags[sidecarKey] = sidecarHedData
            }
          }
        }

        // get all non-empty rows
        const rows = eventFile.contents
          .split('\n')
          .filter(row => !(!row || /^\s*$/.test(row)))

        const columnHeaders = rows[0].trim().split('\t')
        const hedColumnIndex = columnHeaders.indexOf('HED')
        const sidecarHedColumnIndices = {}
        for (const sidecarHedColumn in sidecarHedTags) {
          const sidecarHedColumnHeader = columnHeaders.indexOf(sidecarHedColumn)
          if (sidecarHedColumnHeader > -1) {
            sidecarHedColumnIndices[sidecarHedColumn] = sidecarHedColumnHeader
          }
        }
        if (hedColumnIndex === -1 && sidecarHedColumnIndices.length === 0) {
          return
        }

        for (const row of rows.slice(1)) {
          // get the 'HED' field
          const rowCells = row.trim().split('\t')
          const hedStringParts = []
          if (rowCells[hedColumnIndex] && rowCells[hedColumnIndex] !== 'n/a') {
            hedStringParts.push(rowCells[hedColumnIndex])
          }
          for (const sidecarHedColumn in sidecarHedColumnIndices) {
            const sidecarHedIndex = sidecarHedColumnIndices[sidecarHedColumn]
            const sidecarHedData = sidecarHedTags[sidecarHedColumn]
            const rowCell = rowCells[sidecarHedIndex]
            if (rowCell && rowCell !== 'n/a') {
              let sidecarHedString
              if (!sidecarHedData) {
                continue
              }
              if (typeof sidecarHedData === 'string') {
                sidecarHedString = sidecarHedData.replace('#', rowCell)
              } else {
                sidecarHedString = sidecarHedData[rowCell]
              }
              if (sidecarHedString !== undefined) {
                hedStringParts.push(sidecarHedString)
              } else {
                issues.push(
                  new Issue({
                    code: 112,
                    file: eventFile.file,
                    evidence: rowCell,
                  }),
                )
              }
            }
          }

          if (hedStringParts.length === 0) {
            continue
          }
          hedStrings.push(hedStringParts.join(','))
        }

        if (hedStrings.length > 0) {
          hedStringsFound = true
        }

        for (const hedString of hedStrings) {
          const [
            isHedStringValid,
            hedIssues,
          ] = hedValidator.validator.validateHedEvent(
            hedString,
            hedSchema,
            true,
          )
          if (!isHedStringValid) {
            const convertedIssues = convertHedIssuesToBidsIssues(
              hedIssues,
              eventFile.file,
            )
            issues = issues.concat(convertedIssues)
          }
        }
      })
      if (hedStringsFound && Object.entries(schemaDefinition).length === 0) {
        issues.push(new Issue({ code: 132 }))
      }
      return issues
    })
}

function convertHedIssuesToBidsIssues(hedIssues, file) {
  const hedIssuesToBidsCodes = {
    invalidCharacter: 106,
    parentheses: 107,
    commaMissing: 108,
    capitalization: 109,
    duplicateTag: 110,
    tooManyTildes: 111,
    extraDelimiter: 115,
    invalidTag: 116,
    multipleUniqueTags: 117,
    childRequired: 118,
    requiredPrefixMissing: 119,
    unitClassDefaultUsed: 120,
    unitClassInvalidUnit: 121,
    extraCommaOrInvalid: 122,
    invalidParentNode: 135,
    noValidTagFound: 136,
    emptyTagFound: 137,
    duplicateTagsInSchema: 138,
    extension: 140,
  }

  const convertedIssues = []
  for (const hedIssue of hedIssues) {
    const bidsIssueCode = hedIssuesToBidsCodes[hedIssue.code]
    if (bidsIssueCode === undefined) {
      if (hedIssue.message.startsWith('WARNING')) {
        convertedIssues.push(
          new Issue({
            code: 105,
            file: file,
            evidence: hedIssue.message,
          }),
        )
      } else {
        convertedIssues.push(
          new Issue({
            code: 104,
            file: file,
            evidence: hedIssue.message,
          }),
        )
      }
    } else {
      convertedIssues.push(
        new Issue({
          code: bidsIssueCode,
          file: file,
          evidence: hedIssue.message,
        }),
      )
    }
  }

  return convertedIssues
}
<commit_msg>Use new dataset-level validation entry function instead of a loop<commit_after>import hedValidator from 'hed-validator'
import path from 'path'
import semver from 'semver'
import utils from '../../utils'
const Issue = utils.issues.Issue

export default function checkHedStrings(events, headers, jsonContents, dir) {
  let issues = []

  // find specified version in sidecar
  const schemaDefinition = {}
  const datasetDescription = jsonContents['/dataset_description.json']

  if (datasetDescription && datasetDescription.HEDVersion) {
    if (semver.valid(datasetDescription.HEDVersion)) {
      schemaDefinition.version = datasetDescription.HEDVersion
    } else {
      schemaDefinition.path = path.join(
        path.resolve(dir),
        'sourcedata',
        datasetDescription.HEDVersion,
      )
    }
  }

  // run HED validator
  return hedValidator.validator
    .buildSchema(schemaDefinition)
    .then(hedSchema => {
      let hedStringsFound = false
      const sidecarIssues = {}
      let sidecarIssuesFound = false
      // loop through event data files
      events.forEach(eventFile => {
        const hedStrings = []
        // get the json sidecar dictionary associated with the event data
        const potentialSidecars = utils.files.potentialLocations(
          eventFile.path.replace('.tsv', '.json'),
        )
        // validate the HED strings in the json sidecars
        for (const sidecarName of potentialSidecars) {
          if (!(sidecarName in sidecarIssues)) {
            const sidecarDictionary = jsonContents[sidecarName]
            let sidecarHedStrings = []
            for (const sidecarKey in sidecarDictionary) {
              const sidecarValue = sidecarDictionary[sidecarKey]
              if (
                sidecarValue !== null &&
                typeof sidecarValue === 'object' &&
                sidecarValue.HED !== undefined
              ) {
                if (typeof sidecarValue.HED === 'string') {
                  sidecarHedStrings.push(sidecarValue.HED)
                } else {
                  sidecarHedStrings = sidecarHedStrings.concat(
                    Object.values(sidecarValue.HED),
                  )
                }
              }
            }
            let fileIssues = []
            for (const hedString of sidecarHedStrings) {
              const [
                isHedStringValid,
                hedIssues,
              ] = hedValidator.validator.validateHedString(
                hedString,
                hedSchema,
                true,
                true,
              )
              if (!isHedStringValid) {
                const convertedIssues = convertHedIssuesToBidsIssues(
                  hedIssues,
                  sidecarName,
                )
                fileIssues = fileIssues.concat(convertedIssues)
                sidecarIssuesFound = true
              }
            }
            sidecarIssues[sidecarName] = fileIssues
            if (fileIssues.length > 0) {
              issues.push(new Issue({ code: 139, file: sidecarName }))
              issues = issues.concat(fileIssues)
            }
          } else if (sidecarIssues[sidecarName].length > 0) {
            sidecarIssuesFound = true
          }
        }
        if (sidecarIssuesFound) {
          return
        }
        const mergedDictionary = utils.files.generateMergedSidecarDict(
          potentialSidecars,
          jsonContents,
        )

        const sidecarHedTags = {}
        for (const sidecarKey in mergedDictionary) {
          const sidecarValue = mergedDictionary[sidecarKey]
          if (
            sidecarValue !== null &&
            typeof sidecarValue === 'object' &&
            sidecarValue.HED !== undefined
          ) {
            const sidecarHedData = sidecarValue.HED
            if (
              typeof sidecarHedData === 'string' &&
              sidecarHedData.split('#').length !== 2
            ) {
              issues.push(
                new Issue({
                  code: 134,
                  file: eventFile.file,
                  evidence: sidecarHedData,
                }),
              )
              sidecarHedTags[sidecarKey] = false
            } else {
              sidecarHedTags[sidecarKey] = sidecarHedData
            }
          }
        }

        // get all non-empty rows
        const rows = eventFile.contents
          .split('\n')
          .filter(row => !(!row || /^\s*$/.test(row)))

        const columnHeaders = rows[0].trim().split('\t')
        const hedColumnIndex = columnHeaders.indexOf('HED')
        const sidecarHedColumnIndices = {}
        for (const sidecarHedColumn in sidecarHedTags) {
          const sidecarHedColumnHeader = columnHeaders.indexOf(sidecarHedColumn)
          if (sidecarHedColumnHeader > -1) {
            sidecarHedColumnIndices[sidecarHedColumn] = sidecarHedColumnHeader
          }
        }
        if (hedColumnIndex === -1 && sidecarHedColumnIndices.length === 0) {
          return
        }

        for (const row of rows.slice(1)) {
          // get the 'HED' field
          const rowCells = row.trim().split('\t')
          const hedStringParts = []
          if (rowCells[hedColumnIndex] && rowCells[hedColumnIndex] !== 'n/a') {
            hedStringParts.push(rowCells[hedColumnIndex])
          }
          for (const sidecarHedColumn in sidecarHedColumnIndices) {
            const sidecarHedIndex = sidecarHedColumnIndices[sidecarHedColumn]
            const sidecarHedData = sidecarHedTags[sidecarHedColumn]
            const rowCell = rowCells[sidecarHedIndex]
            if (rowCell && rowCell !== 'n/a') {
              let sidecarHedString
              if (!sidecarHedData) {
                continue
              }
              if (typeof sidecarHedData === 'string') {
                sidecarHedString = sidecarHedData.replace('#', rowCell)
              } else {
                sidecarHedString = sidecarHedData[rowCell]
              }
              if (sidecarHedString !== undefined) {
                hedStringParts.push(sidecarHedString)
              } else {
                issues.push(
                  new Issue({
                    code: 112,
                    file: eventFile.file,
                    evidence: rowCell,
                  }),
                )
              }
            }
          }

          if (hedStringParts.length === 0) {
            continue
          }
          hedStrings.push(hedStringParts.join(','))
        }

        if (hedStrings.length > 0) {
          hedStringsFound = true
        }

        const [
          isHedDatasetValid,
          hedIssues,
        ] = hedValidator.validator.validateHedDataset(
          hedStrings,
          hedSchema,
          true,
        )
        if (!isHedDatasetValid) {
          const convertedIssues = convertHedIssuesToBidsIssues(
            hedIssues,
            eventFile.file,
          )
          issues = issues.concat(convertedIssues)
        }
      })
      if (hedStringsFound && Object.entries(schemaDefinition).length === 0) {
        issues.push(new Issue({ code: 132 }))
      }
      return issues
    })
}

function convertHedIssuesToBidsIssues(hedIssues, file) {
  const hedIssuesToBidsCodes = {
    invalidCharacter: 106,
    parentheses: 107,
    commaMissing: 108,
    capitalization: 109,
    duplicateTag: 110,
    tooManyTildes: 111,
    extraDelimiter: 115,
    invalidTag: 116,
    multipleUniqueTags: 117,
    childRequired: 118,
    requiredPrefixMissing: 119,
    unitClassDefaultUsed: 120,
    unitClassInvalidUnit: 121,
    extraCommaOrInvalid: 122,
    invalidParentNode: 135,
    noValidTagFound: 136,
    emptyTagFound: 137,
    duplicateTagsInSchema: 138,
    extension: 140,
  }

  const convertedIssues = []
  for (const hedIssue of hedIssues) {
    const bidsIssueCode = hedIssuesToBidsCodes[hedIssue.code]
    if (bidsIssueCode === undefined) {
      if (hedIssue.message.startsWith('WARNING')) {
        convertedIssues.push(
          new Issue({
            code: 105,
            file: file,
            evidence: hedIssue.message,
          }),
        )
      } else {
        convertedIssues.push(
          new Issue({
            code: 104,
            file: file,
            evidence: hedIssue.message,
          }),
        )
      }
    } else {
      convertedIssues.push(
        new Issue({
          code: bidsIssueCode,
          file: file,
          evidence: hedIssue.message,
        }),
      )
    }
  }

  return convertedIssues
}
<|endoftext|>


<commit_before>'use strict';

/**
 * Helps using some protractor browser features.
 *
 * @module e2e
 * @class browser
 */

var path = require('path');
var fs = require('fs');
var openVeoAPI = require('@openveo/api');

/**
 * Writes screenshot to a file.
 *
 * @private
 * @static
 * @method writeScreenShot
 * @param {String} data Screenshot data
 * @param {String} file Screenshot final file path
 */
function writeScreenShot(data, file) {
  var stream = fs.createWriteStream(file);
  stream.write(new Buffer(data, 'base64'));
  stream.end();
}

/**
 * Clicks on an element.
 *
 * There are many issues with click events in web drivers, the same click is sometimes performed, and sometimes not.
 * Moving the cursor to the element then performing a click seems to be more stable.
 * It also checks if the element is clickable before clicking on it.
 *
 * Also the banner in position fix prevent the driver to click on elements behind it. To avoid this problem, scroll
 * up to the element minus the height of the banner.
 *
 * @static
 * @method click
 * @param {ElementFinder} elementFinder The element to click
 * @param {Number} [delay] The delay in milliseconds to wait before clicking on the element
 * @return {Promise} Promise resolving when element is clicked
 */
module.exports.click = function(elementFinder, delay) {
  return browser.waitForAngular().then(function() {
    return elementFinder.getLocation();
  }).then(function(coordinates) {
    var pageElement = element(by.id('page-content-wrapper'));
    var script = 'arguments[0].scrollTop = ' + Math.max(coordinates.y - 50, 0) + ';';

    browser.executeScript(script, pageElement.getWebElement());
    browser.wait(protractor.ExpectedConditions.elementToBeClickable(elementFinder), 1000, 'Element is not clickable');
    browser.actions().mouseMove(elementFinder).perform();

    if (delay)
      browser.sleep(delay);

    return browser.actions().click().perform();
  });
};

/**
 * Sets browser's size.
 *
 * @example
 *
 *     var browserAPI = require('@openveo/test').e2e.browser;
 *     browserAPI.setSize(800, 600);
 *
 * @static
 * @method setSize
 * @param {Number} width The browser width in px
 * @param {Number} height The browser height in px
 * @return {Promise} Promise resolving when the browser size is set
 */
module.exports.setSize = function(width, height) {
  return browser.driver.manage().window().setSize(width, height);
};

/**
 * Sets process.protractorConf to the configuration of the actual capability.
 *
 * @example
 *
 *     var browserAPI = require('@openveo/test').e2e.browser;
 *     browserAPI.init();
 *
 * @static
 * @method init
 * @return {Promise} Promise resolving when init is done
 */
module.exports.init = function() {
  return browser.getProcessedConfig().then(function(conf) {
    process.protractorConf = conf;
  });
};

/**
 * Disables all animations.
 *
 * @example
 *
 *     var browserAPI = require('@openveo/test').e2e.browser;
 *     browserAPI.deactivateAnimations();
 *
 * @static
 * @method deactivateAnimations
 */
module.exports.deactivateAnimations = function() {
  var disableCssAnimate = function() {
    angular.module('disableCssAnimate', []).run(function() {
      var style = document.createElement('style');
      style.type = 'text/css';
      style.innerHTML = '* {' +
        '-webkit-transition: none !important;' +
        '-moz-transition: none !important;' +
        '-o-transition: none !important;' +
        '-ms-transition: none !important;' +
        'transition: none !important;' +
      '}';
      document.getElementsByTagName('head')[0].appendChild(style);
    });
  };

  browser.addMockModule('disableCssAnimate', disableCssAnimate);
};

/**
 * Takes a screenshot of the actual browser state.
 *
 * @example
 *
 *     var browserAPI = require('@openveo/test').e2e.browser;
 *     browserAPI.takeScreenshot('/tmp', 'myScreenshot');
 *
 * @static
 * @method takeScreenshot
 * @param {String} file The screenshot file path
 * @param {String} fileName The screenshot file name without the extension
 */
module.exports.takeScreenshot = function(filePath, fileName) {
  browser.takeScreenshot().then(function(png) {
    openVeoAPI.fileSystem.mkdir(filePath, function(error) {
      if (!error)
        writeScreenShot(png, path.join(filePath, fileName + '.png'));
    });
  });
};
<commit_msg>Modify browser.click method to handle select option elements<commit_after>'use strict';

/**
 * Helps using some protractor browser features.
 *
 * @module e2e
 * @class browser
 */

var path = require('path');
var fs = require('fs');
var openVeoAPI = require('@openveo/api');

/**
 * Writes screenshot to a file.
 *
 * @private
 * @static
 * @method writeScreenShot
 * @param {String} data Screenshot data
 * @param {String} file Screenshot final file path
 */
function writeScreenShot(data, file) {
  var stream = fs.createWriteStream(file);
  stream.write(new Buffer(data, 'base64'));
  stream.end();
}

/**
 * Clicks on an element.
 *
 * There are many issues with click events in web drivers, the same click is sometimes performed, and sometimes not.
 * Moving the cursor to the element then performing a click seems to be more stable.
 * It also checks if the element is clickable before clicking on it.
 *
 * Also the banner in position fix prevent the driver to click on elements behind it. To avoid this problem, scroll
 * up to the element minus the height of the banner.
 *
 * @static
 * @method click
 * @param {ElementFinder} elementFinder The element to click
 * @param {Number} [delay] The delay in milliseconds to wait before clicking on the element
 * @return {Promise} Promise resolving when element is clicked
 */
module.exports.click = function(elementFinder, delay) {
  return browser.waitForAngular().then(function() {
    return elementFinder.getLocation();
  }).then(function(coordinates) {
    var pageElement = element(by.id('page-content-wrapper'));
    var script = 'arguments[0].scrollTop = ' + Math.max(coordinates.y - 50, 0) + ';';

    browser.executeScript(script, pageElement.getWebElement());
    return elementFinder.getTagName();
  }).then(function(tagName) {

    // Select options clicks are automatic, clicking on the option element will automatically perform several
    // actions : Moving to select element, clicking on the select element, moving to option element and
    // clicking on the action element
    if (tagName === 'option')
      return elementFinder.click();
    else {
      browser.wait(protractor.ExpectedConditions.elementToBeClickable(elementFinder), 1000, 'Element is not clickable');
      browser.actions().mouseMove(elementFinder).perform();

      if (delay)
        browser.sleep(delay);

      return browser.actions().click().perform();
    }
  });
};

/**
 * Sets browser's size.
 *
 * @example
 *
 *     var browserAPI = require('@openveo/test').e2e.browser;
 *     browserAPI.setSize(800, 600);
 *
 * @static
 * @method setSize
 * @param {Number} width The browser width in px
 * @param {Number} height The browser height in px
 * @return {Promise} Promise resolving when the browser size is set
 */
module.exports.setSize = function(width, height) {
  return browser.driver.manage().window().setSize(width, height);
};

/**
 * Sets process.protractorConf to the configuration of the actual capability.
 *
 * @example
 *
 *     var browserAPI = require('@openveo/test').e2e.browser;
 *     browserAPI.init();
 *
 * @static
 * @method init
 * @return {Promise} Promise resolving when init is done
 */
module.exports.init = function() {
  return browser.getProcessedConfig().then(function(conf) {
    process.protractorConf = conf;
  });
};

/**
 * Disables all animations.
 *
 * @example
 *
 *     var browserAPI = require('@openveo/test').e2e.browser;
 *     browserAPI.deactivateAnimations();
 *
 * @static
 * @method deactivateAnimations
 */
module.exports.deactivateAnimations = function() {
  var disableCssAnimate = function() {
    angular.module('disableCssAnimate', []).run(function() {
      var style = document.createElement('style');
      style.type = 'text/css';
      style.innerHTML = '* {' +
        '-webkit-transition: none !important;' +
        '-moz-transition: none !important;' +
        '-o-transition: none !important;' +
        '-ms-transition: none !important;' +
        'transition: none !important;' +
      '}';
      document.getElementsByTagName('head')[0].appendChild(style);
    });
  };

  browser.addMockModule('disableCssAnimate', disableCssAnimate);
};

/**
 * Takes a screenshot of the actual browser state.
 *
 * @example
 *
 *     var browserAPI = require('@openveo/test').e2e.browser;
 *     browserAPI.takeScreenshot('/tmp', 'myScreenshot');
 *
 * @static
 * @method takeScreenshot
 * @param {String} file The screenshot file path
 * @param {String} fileName The screenshot file name without the extension
 */
module.exports.takeScreenshot = function(filePath, fileName) {
  browser.takeScreenshot().then(function(png) {
    openVeoAPI.fileSystem.mkdir(filePath, function(error) {
      if (!error)
        writeScreenShot(png, path.join(filePath, fileName + '.png'));
    });
  });
};
<|endoftext|>


<commit_before>/*
 *  Copyright 2011 Rackspace
 *
 *  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 sprintf = require('./sprintf').sprintf;
var XMLParser = require('./parser').XMLParser;
var TreeBuilder = require('./parser').TreeBuilder;

function merge(a, b) {
  var c = {}, attrname;

  for (attrname in a) {
    if (a.hasOwnProperty(attrname)) {
      c[attrname] = a[attrname];
    }
  }
  for (attrname in b) {
    if (b.hasOwnProperty(attrname)) {
      c[attrname] = b[attrname];
    }
  }
  return c;
};

var element_ids = 0;

function Element(tag, attrib)
{
  this._id = element_ids++;
  this.tag = tag;
  this.attrib = {};
  this.text = null;
  this.tail = null;
  this._children = [];

  if (attrib) {
    this.attrib = merge(this.attrib, attrib);
  }
}

Element.prototype.toString = function()
{
  return sprintf("<Element %s at %s>", this.tag, this._id)
};

Element.prototype.makeelement = function(tag, attrib)
{
  return new Element(tag, attrib);
};

Element.prototype.len = function()
{
  return this._children.length;
};

Element.prototype.getItem = function(index)
{
  return this._children[index];
};

Element.prototype.setItem = function(index, element)
{
  this._children[index] = element;
};

Element.prototype.delItem = function(index)
{
  this._children.splice(index, 1);
};

Element.prototype.getSlice = function(start, stop)
{
  return this._children.slice(start, stop);
};

Element.prototype.setSlice = function(start, stop, elements)
{
  var i;
  var k = 0;
  for (i = start; i < stop; i++, k++) {
    this._children[i] = elements[k];
  }
  return rv;
};

Element.prototype.delSlice = function(start, stop)
{
  this._children.splice(start, stop - start);
};

Element.prototype.append = function(element)
{
  this._children.push(element);
};

Element.prototype.extend = function(elements)
{
  this._children.concat(elements);
};

Element.prototype.insert = function(index, element)
{
  this._children[index] = element;
};

Element.prototype.remove = function(index, element)
{
  this._children = this._children.filter(function(e) {
    /* TODO: is this the right way to do this? */
    if (e._id == element._id) {
      return false;
    }
    return true;
  });
};

Element.prototype.find = function(path)
{
  throw new Error('element.find not implemented');
};

Element.prototype.findtext = function(path, defvalue)
{
  throw new Error('element.findtext not implemented');
};

Element.prototype.findall = function(path, defvalue)
{
  throw new Error('element.findall not implemented');
};

Element.prototype.clear = function()
{
  this.attrib = {};
  this._children = [];
  this.text = null;
  this.tail = null;
};

Element.prototype.get = function(key, defvalue)
{
  if (this.attrib[key] !== undefined) {
    return this.attrib[key];
  }
  else {
    return defvalue;
  }
};

Element.prototype.set = function(key, value)
{
  this.attrib[key] = value;
};

Element.prototype.keys = function()
{
  return Object.keys(this.attrib);
};

Element.prototype.items = function()
{
  var rv = [];
  for (var k in this.attrib) {
    if (this.attrib.hasOwnProperty(k)) {
      rv.push([k, this.attrib[k]]);
    }
  }
  return rv;
};

/*
 * In python this uses a generator, but in v8 we don't have em,
 * so we use a callback instead.
 **/
Element.prototype.iter = function(tag, callback)
{
  var self = this;
  var i, child;

  if (tag == "*") {
    tag = null;
  }

  if (tag === null || this.tag == tag) {
    callback(self);
  }

  for (i = 0; i < this._children.length; i++) {
    child = this._children[i];
    child.iter(tag, function(e) {
      callback(e);
    });
  }
};

Element.prototype.itertext = function(callback)
{
  this.iter(null, function(e) {
    if (e.text) {
      callback(e.text);
    }

    if (e.tail) {
      callback(e.tail);
    }
  })
};

function SubElement(parent, tag, attrib) {
  element = parent.makeelement(tag, attrib);
  parent.append(element);
  return element;
};

function Comment(text) {
  var element = new Element(Comment);
  if (text) {
    element.text = text;
  }
  return element;
};

function ProcessingInstruction(target, text)
{
  var element = new Element(ProcessingInstruction);
  element.text = target;
  if (text) {
    element.text = element.text + " " + text;
  }
  return element;
}

function QName(text_or_uri, tag)
{
  if (tag) {
    text_or_uri = sprintf("{%s}%s", text_or_uri, tag)
  }
  this.text = text_or_uri;
};

function ElementTree(element)
{
  this._root = element;
};

ElementTree.prototype.getroot = function() {
  return this._root;
};

ElementTree.prototype._setroot = function(element) {
  this._root = element;
};

ElementTree.prototype.parse = function(source, parser) {
  if (!parser) {
    parser = new XMLParser(new TreeBuilder());
  }
  parser.feed(source);
  this._root = parser.close();
  return this._root;
};

ElementTree.prototype.iter = function(tag, callback) {
  this._root.iter(tag, callback);
};

ElementTree.prototype.find = function(path) {
  return this._root.find(path);
};

ElementTree.prototype.findtext = function(path, defvalue) {
  return this._root.findtext(path, defvalue);
};

ElementTree.prototype.findall = function(path) {
  return this._root.findall(path);
};

/**
 * Unlike ElementTree, we don't write to a file, we return you a string.
 */
ElementTree.prototype.write = function(options) {
  var sb = [];
  options = merge({
    encoding: 'utf-8',
    xml_declaration: null,
    default_namespace: null,
    method: 'xml'}, options);

  if (options.xml_declaration !== false) {
    sb.push("<?xml version='1.0' encoding='"+options.encoding +"'?>\n")
  }

  if (options.method == "text") {
    _serialize_text(sb, self._root, encoding);
  }
  else {
    var qnames, namespaces;
    var x = _namespaces(this._root, options.encoding, options.default_namespace);
    qnames = x[0];
    namespaces = x[1];

    if (options.method == "xml") {
      _serialize_xml(function(data) {
        sb.push(data);
      }, this._root, options.encoding, qnames, namespaces);
    }
    else {
      /* TODO: html */
      throw new Error("unknown serialization method "+ options.method);
    }
  }

  return sb.join("");
};

var _namespace_map = {
    /* "well-known" namespace prefixes */
    "http://www.w3.org/XML/1998/namespace": "xml",
    "http://www.w3.org/1999/xhtml": "html",
    "http://www.w3.org/1999/02/22-rdf-syntax-ns#": "rdf",
    "http://schemas.xmlsoap.org/wsdl/": "wsdl",
    /* xml schema */
    "http://www.w3.org/2001/XMLSchema": "xs",
    "http://www.w3.org/2001/XMLSchema-instance": "xsi",
    /* dublic core */
    "http://purl.org/dc/elements/1.1/": "dc",
}

/* TODO: benchmark single regex */
function _escape_attrib(text, encoding) {
  /* TODO: handle encoding */
  if (text.indexOf("&") !== -1) {
    text = text.replace("&", "&");
  }
  if (text.indexOf("<") !== -1) {
    text = text.replace("<", "<");
  }
  if (text.indexOf(">") !== -1) {
    text = text.replace(">", ">");
  }
  if (text.indexOf("\"") !== -1) {
    text = text.replace("\"", """);
  }
  if (text.indexOf("\n") !== -1) {
    text = text.replace("\n", "
");
  }

  return text;
};

function _escape_cdata(text, encoding) {
  /* TODO: handle encoding */
  if (text.indexOf("&") !== -1) {
    text = text.replace("&", "&");
  }
  if (text.indexOf("<") !== -1) {
    text = text.replace("<", "<");
  }
  if (text.indexOf(">") !== -1) {
    text = text.replace(">", ">");
  }

  return text;
};

function _namespaces(elem, encoding, default_namespace) {
  var qnames = {};
  var namespaces = {};

  if (default_namespace) {
    namespaces[default_namespace] = ""
  }

  function add_qname(qname) {
    /* TODO: qname support */
    if (qname[0] == "{") {
      var tmp = qname.substring(1).split("}", 2);
      var uri = tmp[0];
      var tag = tmp[1];
      var prefix = namespaces[uri];
      if (prefix === undefined) {
        prefix = _namespace_map[uri];
        if (prefix === undefined) {
          prefix = "ns" + namespaces.length;
        }
        if (prefix != "xml") {
          namespaces[uri] = prefix;
        }
      }

      if (prefix) {
        qnames[qname] = sprintf("%s:%s", prefix, tag);
      }
      else {
        qnames[qname] = tag;
      }
    }
    else {
      /* TOOD: default namespaces (?) */
      qnames[qname] = qname;
    }
  }

  elem.iter(null, function(e) {
    var i;
    var tag = e.tag;
    var text = e.text;
    var items = e.items();

    if (tag instanceof QName && qnames[tag.text] === undefined) {
      add_qname(tag.text);
    }
    else if (typeof(tag) === "string") {
      add_qname(tag)
    }
    else if (tag !== null && tag != Comment && tag != ProcessingInstruction) {
      throw new Error('Invalid tag type for serialization: '+ tag);
    }

    if (text instanceof QName && qnames[text.text] === undefined) {
      add_qname(text.text);
    }

    items.forEach(function(item) {
      var key = item[0],
          value = item[1];
      if (key instanceof QName) {
        key = key.text;
      }

      if (qnames[key] === undefined) {
        add_qname(key);
      }

      if (value instanceof QName && qnames[value.text] === undefined) {
        add_qname(value.text);
      }
    });
  });
  return [qnames, namespaces]
};

function _serialize_xml(write, elem, encoding, qnames, namespaces) {
  var tag = elem.tag;
  var text = elem.text;
  var items;
  var i;

  if (tag === Comment) {
    write(sprintf("<!--%s-->", _escape_cdata(text, encoding)));
  }
  else if (tag === ProcessingInstruction) {
    write(sprintf("<?%s?>", _escape_cdata(text, encoding)));
  }
  else {
    tag = qnames[tag];
    if (tag === undefined) {
      if (text) {
        write(_escape_cdata(text, encoding));
      }
      elem.iter(function(e) {
        _serialize_xml(write, e, encoding, qnames, null);
      });
    }
    else {
      write("<" + tag);
      items = elem.items();
      items.sort();
      items.forEach(function(item) {
        var k = item[0],
            v = item[1];

          if (k instanceof QName) {
            k = k.text;
          }

          if (v instanceof QName) {
            v = qnames[v.text];
          }
          else {
            v = _escape_attrib(v, encoding)
          }
          write(sprintf(" %s=\"%s\"", qnames[k], v));
      });

      if (namespaces) {
        /* TODO: write xmlns */
      }

      if (text || elem.len()) {
        write(">");
        if (text) {
          write(_escape_cdata(text, encoding))
        }
        elem._children.forEach(function(e) {
          _serialize_xml(write, e, encoding, qnames, null);
        });

        write("</" + tag + ">");
      }
      else {
        write(" />");
      }
    }
  }
};

exports.PI = ProcessingInstruction;
exports.Comment = Comment;
exports.ProcessingInstruction = ProcessingInstruction;
exports.SubElement = SubElement;
exports.ElementTree = ElementTree;
exports.Element = function(tag, attrib) {
  return new Element(tag, attrib);
};
<commit_msg>Add XML() shortcut<commit_after>/*
 *  Copyright 2011 Rackspace
 *
 *  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 sprintf = require('./sprintf').sprintf;
var XMLParser = require('./parser').XMLParser;
var TreeBuilder = require('./parser').TreeBuilder;

function merge(a, b) {
  var c = {}, attrname;

  for (attrname in a) {
    if (a.hasOwnProperty(attrname)) {
      c[attrname] = a[attrname];
    }
  }
  for (attrname in b) {
    if (b.hasOwnProperty(attrname)) {
      c[attrname] = b[attrname];
    }
  }
  return c;
};

var element_ids = 0;

function Element(tag, attrib)
{
  this._id = element_ids++;
  this.tag = tag;
  this.attrib = {};
  this.text = null;
  this.tail = null;
  this._children = [];

  if (attrib) {
    this.attrib = merge(this.attrib, attrib);
  }
}

Element.prototype.toString = function()
{
  return sprintf("<Element %s at %s>", this.tag, this._id)
};

Element.prototype.makeelement = function(tag, attrib)
{
  return new Element(tag, attrib);
};

Element.prototype.len = function()
{
  return this._children.length;
};

Element.prototype.getItem = function(index)
{
  return this._children[index];
};

Element.prototype.setItem = function(index, element)
{
  this._children[index] = element;
};

Element.prototype.delItem = function(index)
{
  this._children.splice(index, 1);
};

Element.prototype.getSlice = function(start, stop)
{
  return this._children.slice(start, stop);
};

Element.prototype.setSlice = function(start, stop, elements)
{
  var i;
  var k = 0;
  for (i = start; i < stop; i++, k++) {
    this._children[i] = elements[k];
  }
  return rv;
};

Element.prototype.delSlice = function(start, stop)
{
  this._children.splice(start, stop - start);
};

Element.prototype.append = function(element)
{
  this._children.push(element);
};

Element.prototype.extend = function(elements)
{
  this._children.concat(elements);
};

Element.prototype.insert = function(index, element)
{
  this._children[index] = element;
};

Element.prototype.remove = function(index, element)
{
  this._children = this._children.filter(function(e) {
    /* TODO: is this the right way to do this? */
    if (e._id == element._id) {
      return false;
    }
    return true;
  });
};

Element.prototype.find = function(path)
{
  throw new Error('element.find not implemented');
};

Element.prototype.findtext = function(path, defvalue)
{
  throw new Error('element.findtext not implemented');
};

Element.prototype.findall = function(path, defvalue)
{
  throw new Error('element.findall not implemented');
};

Element.prototype.clear = function()
{
  this.attrib = {};
  this._children = [];
  this.text = null;
  this.tail = null;
};

Element.prototype.get = function(key, defvalue)
{
  if (this.attrib[key] !== undefined) {
    return this.attrib[key];
  }
  else {
    return defvalue;
  }
};

Element.prototype.set = function(key, value)
{
  this.attrib[key] = value;
};

Element.prototype.keys = function()
{
  return Object.keys(this.attrib);
};

Element.prototype.items = function()
{
  var rv = [];
  for (var k in this.attrib) {
    if (this.attrib.hasOwnProperty(k)) {
      rv.push([k, this.attrib[k]]);
    }
  }
  return rv;
};

/*
 * In python this uses a generator, but in v8 we don't have em,
 * so we use a callback instead.
 **/
Element.prototype.iter = function(tag, callback)
{
  var self = this;
  var i, child;

  if (tag == "*") {
    tag = null;
  }

  if (tag === null || this.tag == tag) {
    callback(self);
  }

  for (i = 0; i < this._children.length; i++) {
    child = this._children[i];
    child.iter(tag, function(e) {
      callback(e);
    });
  }
};

Element.prototype.itertext = function(callback)
{
  this.iter(null, function(e) {
    if (e.text) {
      callback(e.text);
    }

    if (e.tail) {
      callback(e.tail);
    }
  })
};

function SubElement(parent, tag, attrib) {
  element = parent.makeelement(tag, attrib);
  parent.append(element);
  return element;
};

function Comment(text) {
  var element = new Element(Comment);
  if (text) {
    element.text = text;
  }
  return element;
};

function ProcessingInstruction(target, text)
{
  var element = new Element(ProcessingInstruction);
  element.text = target;
  if (text) {
    element.text = element.text + " " + text;
  }
  return element;
}

function QName(text_or_uri, tag)
{
  if (tag) {
    text_or_uri = sprintf("{%s}%s", text_or_uri, tag)
  }
  this.text = text_or_uri;
};

function ElementTree(element)
{
  this._root = element;
};

ElementTree.prototype.getroot = function() {
  return this._root;
};

ElementTree.prototype._setroot = function(element) {
  this._root = element;
};

ElementTree.prototype.parse = function(source, parser) {
  if (!parser) {
    parser = new XMLParser(new TreeBuilder());
  }
  parser.feed(source);
  this._root = parser.close();
  return this._root;
};

ElementTree.prototype.iter = function(tag, callback) {
  this._root.iter(tag, callback);
};

ElementTree.prototype.find = function(path) {
  return this._root.find(path);
};

ElementTree.prototype.findtext = function(path, defvalue) {
  return this._root.findtext(path, defvalue);
};

ElementTree.prototype.findall = function(path) {
  return this._root.findall(path);
};

/**
 * Unlike ElementTree, we don't write to a file, we return you a string.
 */
ElementTree.prototype.write = function(options) {
  var sb = [];
  options = merge({
    encoding: 'utf-8',
    xml_declaration: null,
    default_namespace: null,
    method: 'xml'}, options);

  if (options.xml_declaration !== false) {
    sb.push("<?xml version='1.0' encoding='"+options.encoding +"'?>\n")
  }

  if (options.method == "text") {
    _serialize_text(sb, self._root, encoding);
  }
  else {
    var qnames, namespaces;
    var x = _namespaces(this._root, options.encoding, options.default_namespace);
    qnames = x[0];
    namespaces = x[1];

    if (options.method == "xml") {
      _serialize_xml(function(data) {
        sb.push(data);
      }, this._root, options.encoding, qnames, namespaces);
    }
    else {
      /* TODO: html */
      throw new Error("unknown serialization method "+ options.method);
    }
  }

  return sb.join("");
};

var _namespace_map = {
    /* "well-known" namespace prefixes */
    "http://www.w3.org/XML/1998/namespace": "xml",
    "http://www.w3.org/1999/xhtml": "html",
    "http://www.w3.org/1999/02/22-rdf-syntax-ns#": "rdf",
    "http://schemas.xmlsoap.org/wsdl/": "wsdl",
    /* xml schema */
    "http://www.w3.org/2001/XMLSchema": "xs",
    "http://www.w3.org/2001/XMLSchema-instance": "xsi",
    /* dublic core */
    "http://purl.org/dc/elements/1.1/": "dc",
}

/* TODO: benchmark single regex */
function _escape_attrib(text, encoding) {
  /* TODO: handle encoding */
  if (text.indexOf("&") !== -1) {
    text = text.replace("&", "&");
  }
  if (text.indexOf("<") !== -1) {
    text = text.replace("<", "<");
  }
  if (text.indexOf(">") !== -1) {
    text = text.replace(">", ">");
  }
  if (text.indexOf("\"") !== -1) {
    text = text.replace("\"", """);
  }
  if (text.indexOf("\n") !== -1) {
    text = text.replace("\n", "
");
  }

  return text;
};

function _escape_cdata(text, encoding) {
  /* TODO: handle encoding */
  if (text.indexOf("&") !== -1) {
    text = text.replace("&", "&");
  }
  if (text.indexOf("<") !== -1) {
    text = text.replace("<", "<");
  }
  if (text.indexOf(">") !== -1) {
    text = text.replace(">", ">");
  }

  return text;
};

function _namespaces(elem, encoding, default_namespace) {
  var qnames = {};
  var namespaces = {};

  if (default_namespace) {
    namespaces[default_namespace] = ""
  }

  function add_qname(qname) {
    /* TODO: qname support */
    if (qname[0] == "{") {
      var tmp = qname.substring(1).split("}", 2);
      var uri = tmp[0];
      var tag = tmp[1];
      var prefix = namespaces[uri];
      if (prefix === undefined) {
        prefix = _namespace_map[uri];
        if (prefix === undefined) {
          prefix = "ns" + namespaces.length;
        }
        if (prefix != "xml") {
          namespaces[uri] = prefix;
        }
      }

      if (prefix) {
        qnames[qname] = sprintf("%s:%s", prefix, tag);
      }
      else {
        qnames[qname] = tag;
      }
    }
    else {
      /* TOOD: default namespaces (?) */
      qnames[qname] = qname;
    }
  }

  elem.iter(null, function(e) {
    var i;
    var tag = e.tag;
    var text = e.text;
    var items = e.items();

    if (tag instanceof QName && qnames[tag.text] === undefined) {
      add_qname(tag.text);
    }
    else if (typeof(tag) === "string") {
      add_qname(tag)
    }
    else if (tag !== null && tag != Comment && tag != ProcessingInstruction) {
      throw new Error('Invalid tag type for serialization: '+ tag);
    }

    if (text instanceof QName && qnames[text.text] === undefined) {
      add_qname(text.text);
    }

    items.forEach(function(item) {
      var key = item[0],
          value = item[1];
      if (key instanceof QName) {
        key = key.text;
      }

      if (qnames[key] === undefined) {
        add_qname(key);
      }

      if (value instanceof QName && qnames[value.text] === undefined) {
        add_qname(value.text);
      }
    });
  });
  return [qnames, namespaces]
};

function _serialize_xml(write, elem, encoding, qnames, namespaces) {
  var tag = elem.tag;
  var text = elem.text;
  var items;
  var i;

  if (tag === Comment) {
    write(sprintf("<!--%s-->", _escape_cdata(text, encoding)));
  }
  else if (tag === ProcessingInstruction) {
    write(sprintf("<?%s?>", _escape_cdata(text, encoding)));
  }
  else {
    tag = qnames[tag];
    if (tag === undefined) {
      if (text) {
        write(_escape_cdata(text, encoding));
      }
      elem.iter(function(e) {
        _serialize_xml(write, e, encoding, qnames, null);
      });
    }
    else {
      write("<" + tag);
      items = elem.items();
      items.sort();
      items.forEach(function(item) {
        var k = item[0],
            v = item[1];

          if (k instanceof QName) {
            k = k.text;
          }

          if (v instanceof QName) {
            v = qnames[v.text];
          }
          else {
            v = _escape_attrib(v, encoding)
          }
          write(sprintf(" %s=\"%s\"", qnames[k], v));
      });

      if (namespaces) {
        /* TODO: write xmlns */
      }

      if (text || elem.len()) {
        write(">");
        if (text) {
          write(_escape_cdata(text, encoding))
        }
        elem._children.forEach(function(e) {
          _serialize_xml(write, e, encoding, qnames, null);
        });

        write("</" + tag + ">");
      }
      else {
        write(" />");
      }
    }
  }
};

exports.PI = ProcessingInstruction;
exports.Comment = Comment;
exports.ProcessingInstruction = ProcessingInstruction;
exports.SubElement = SubElement;
exports.ElementTree = ElementTree;
exports.Element = function(tag, attrib) {
  return new Element(tag, attrib);
};

exports.XML = function(data) {
  var et = new ElementTree();
  return et.parse(data);
};
<|endoftext|>