<commit_before>import { assertTrimmedNonEmptyString, assertRegExp } from './assert';
import { uniq, each, reduce, isString, isNumber, calcSearchString, parseSearchString } from './utils';

const calcUrlProps = (url) => {
  return {
    path: url.replace(/[?#].*$/, ''),
    searchParams: url.replace(/^[^?]*[?]?/, '').split('&').filter(Boolean)
  };
};

export default class Route {
  constructor (name, { url = '', params = {}, slots = {}, reroute = null } = {}) {
    assertTrimmedNonEmptyString('name', name);
    assertRegExp(/^([a-zA-Z0-9-.]+)$/, 'name', name);

    const parsedUrl = calcUrlProps(url);

    this.url = url;
    this.name = name;
    this.path = parsedUrl.path;
    this.slots = slots;
    this.params = params;
    this.reroute = reroute; // function for replace routes
    this.searchParams = parsedUrl.searchParams;
    this.pathMaskParams = [];

    this.pathMask = this.path.replace(/:([^/]+)/g, ($0, paramName) => {
      this.pathMaskParams.push(paramName);

      return `{{${paramName}}}`;
    });

    this.pathMaskExpSrc = this.path.replace(/:([^/]+)/g, () => '([^/]+)');

    this.pathMaskExp = new RegExp(`^${this.pathMaskExpSrc}$`);

    this.availableUrlParams = this.searchParams.concat(this.pathMaskParams);
    this.additionalParams = Object.keys(this.params)
      .filter((param) => !this.pathMaskParams.includes(param) && !this.searchParams.includes(param));

    this.availableParams = this.availableUrlParams.concat(this.additionalParams);

    // post checking
    if (this.availableUrlParams.length !== uniq(this.availableUrlParams).length) {
      throw new Error(`this "${this.name}" has duplication in params [${this.searchParams.join(',')}] and [${this.pathMaskParams.join(',')}]`);
    }

    const invalidParams = this.additionalParams.filter((param) => !/^[^a-zA-Z]/.test(param));

    if (invalidParams.length) {
      throw new Error(`this "${this.name}" has invalid params [${invalidParams.join(',')}]. additional params must not starts from [a-zA-Z] symbold`);
    }

    const invalidUrlParams = this.availableUrlParams.filter((param) => /^[^a-zA-Z]/.test(param));

    if (invalidUrlParams.length) {
      throw new Error(`this "${this.name}" has invalid url params [${invalidUrlParams.join(',')}]. url params must starts only from [a-z] symbols`);
    }

    each(this.params, (val, key) => {
      if (!this.availableUrlParams.includes(key)) {
        return;
      }

      if (!isString(val) && !isNumber(val)) {
        throw new Error(`this "${this.name}" has invalid param type of "${key}". must be Number or String`);
      }
    });
  }

  isMatch (location, action) {
    return this.pathMaskExp.test(location.pathname || '');
  }

  parseLocationParams ({ pathname, search, hash }) { // location
    let params = { ...this.params };

    pathname = String(pathname || '');

    pathname.replace(this.pathMaskExp, ($0, ...args) => {
      params = this.pathMaskParams.reduce((params, paramName, index) => {
        params[paramName] = args[index];

        return params;
      }, params);

      return $0;
    });

    const searchParams = search ? parseSearchString(String(search || '').replace(/^\?/, '')) : {};

    params = reduce(searchParams, (params, paramValue, paramName) => {
      if (this.searchParams.includes(paramName)) {
        params[paramName] = paramValue;
      }

      return params;
    }, params);

    return params;
  }

  calcLocation (params) {
    const pathname = this.pathMask.replace(/{{([^}]+)}}/g, ($0, paramName) => {
      if (params.hasOwnProperty(paramName)) {
        return params[paramName];
      }

      throw new Error(`Route param "${paramName}" was not defined for route "${this.name}"`);
    });

    const searchParams = this.searchParams.reduce((search, searchParam) => {
      if (params.hasOwnProperty(searchParam) && params[searchParam] !== this.params[searchParam]) {
        search[searchParam] = params[searchParam];
      }

      return search;
    }, {});

    const location = { pathname, hash: '', search: '' };

    if (searchParams) {
      location.search = calcSearchString(searchParams);
    }

    return location;
  }

  calcParams (stateParams, currentStateParams = {}, resetParams = false) {
    return this.availableParams.reduce((params, paramName) => {
      if (stateParams.hasOwnProperty(paramName)) {
        params[paramName] = stateParams[paramName];
      } else if (!resetParams && currentStateParams.hasOwnProperty(paramName)) {
        params[paramName] = currentStateParams[paramName];
      }

      return params;
    }, { ...this.params });
  }

  static createRootRoute (name, definition) {
    const sections = String(name).split('.');

    if (sections.length !== 1) {
      throw new Error(`state "${name}" has invalid format`);
    }

    return new Route(name, definition);
  }

  createChildRoute (name, { url = '', params = {}, slots = {}, reroute = null } = {}) {
    const sections = String(name).split('.');

    if (`${this.name}.${sections[sections.length - 1]}` !== name) {
      throw new Error(`child state name (${name}) must starts from parent name "${this.name}"`);
    }

    const { searchParams, path } = calcUrlProps(url);

    slots = { ...this.slots, ...slots };
    params = { ...this.params, ...params };

    const urlPath = `${this.path.replace(/\/$/, '')}/${path.replace(/^\//, '')}`;
    const urlSearch = `${[ ...this.searchParams, ...searchParams ].join('&')}`;

    url = `${urlPath}${urlSearch ? `?${urlSearch}` : ''}`;

    return new Route(name, { slots, params, url, reroute });
  }
}
<commit_msg>replace spread to Object.assign<commit_after>import { assertTrimmedNonEmptyString, assertRegExp } from './assert';
import { uniq, each, reduce, isString, isNumber, calcSearchString, parseSearchString } from './utils';

const calcUrlProps = (url) => {
  return {
    path: url.replace(/[?#].*$/, ''),
    searchParams: url.replace(/^[^?]*[?]?/, '').split('&').filter(Boolean)
  };
};

export default class Route {
  constructor (name, { url = '', params = {}, slots = {}, reroute = null } = {}) {
    assertTrimmedNonEmptyString('name', name);
    assertRegExp(/^([a-zA-Z0-9-.]+)$/, 'name', name);

    const parsedUrl = calcUrlProps(url);

    this.url = url;
    this.name = name;
    this.path = parsedUrl.path;
    this.slots = slots;
    this.params = params;
    this.reroute = reroute; // function for replace routes
    this.searchParams = parsedUrl.searchParams;
    this.pathMaskParams = [];

    this.pathMask = this.path.replace(/:([^/]+)/g, ($0, paramName) => {
      this.pathMaskParams.push(paramName);

      return `{{${paramName}}}`;
    });

    this.pathMaskExpSrc = this.path.replace(/:([^/]+)/g, () => '([^/]+)');

    this.pathMaskExp = new RegExp(`^${this.pathMaskExpSrc}$`);

    this.availableUrlParams = this.searchParams.concat(this.pathMaskParams);
    this.additionalParams = Object.keys(this.params)
      .filter((param) => !this.pathMaskParams.includes(param) && !this.searchParams.includes(param));

    this.availableParams = this.availableUrlParams.concat(this.additionalParams);

    // post checking
    if (this.availableUrlParams.length !== uniq(this.availableUrlParams).length) {
      throw new Error(`this "${this.name}" has duplication in params [${this.searchParams.join(',')}] and [${this.pathMaskParams.join(',')}]`);
    }

    const invalidParams = this.additionalParams.filter((param) => !/^[^a-zA-Z]/.test(param));

    if (invalidParams.length) {
      throw new Error(`this "${this.name}" has invalid params [${invalidParams.join(',')}]. additional params must not starts from [a-zA-Z] symbold`);
    }

    const invalidUrlParams = this.availableUrlParams.filter((param) => /^[^a-zA-Z]/.test(param));

    if (invalidUrlParams.length) {
      throw new Error(`this "${this.name}" has invalid url params [${invalidUrlParams.join(',')}]. url params must starts only from [a-z] symbols`);
    }

    each(this.params, (val, key) => {
      if (!this.availableUrlParams.includes(key)) {
        return;
      }

      if (!isString(val) && !isNumber(val)) {
        throw new Error(`this "${this.name}" has invalid param type of "${key}". must be Number or String`);
      }
    });
  }

  isMatch (location, action) {
    return this.pathMaskExp.test(location.pathname || '');
  }

  parseLocationParams (location) { // location
    const { pathname, search, hash } = location;
    let params = Object.assign({}, this.params);

    String(pathname || '').replace(this.pathMaskExp, ($0, ...args) => {
      params = this.pathMaskParams.reduce((params, paramName, index) => {
        params[paramName] = args[index];

        return params;
      }, params);

      return $0;
    });

    const searchParams = search ? parseSearchString(String(search || '').replace(/^\?/, '')) : {};

    params = reduce(searchParams, (params, paramValue, paramName) => {
      if (this.searchParams.includes(paramName)) {
        params[paramName] = paramValue;
      }

      return params;
    }, params);

    return params;
  }

  calcLocation (params) {
    const pathname = this.pathMask.replace(/{{([^}]+)}}/g, ($0, paramName) => {
      if (params.hasOwnProperty(paramName)) {
        return params[paramName];
      }

      throw new Error(`Route param "${paramName}" was not defined for route "${this.name}"`);
    });

    const searchParams = this.searchParams.reduce((search, searchParam) => {
      if (params.hasOwnProperty(searchParam) && params[searchParam] !== this.params[searchParam]) {
        search[searchParam] = params[searchParam];
      }

      return search;
    }, {});

    const location = { pathname, hash: '', search: '' };

    if (searchParams) {
      location.search = calcSearchString(searchParams);
    }

    return location;
  }

  calcParams (stateParams, currentStateParams = {}, resetParams = false) {
    return this.availableParams.reduce((params, paramName) => {
      if (stateParams.hasOwnProperty(paramName)) {
        params[paramName] = stateParams[paramName];
      } else if (!resetParams && currentStateParams.hasOwnProperty(paramName)) {
        params[paramName] = currentStateParams[paramName];
      }

      return params;
    }, Object.assign({}, this.params));
  }

  static createRootRoute (name, definition) {
    const sections = String(name).split('.');

    if (sections.length !== 1) {
      throw new Error(`state "${name}" has invalid format`);
    }

    return new Route(name, definition);
  }

  createChildRoute (name, { url = '', params = {}, slots = {}, reroute = null } = {}) {
    const sections = String(name).split('.');

    if (`${this.name}.${sections[sections.length - 1]}` !== name) {
      throw new Error(`child state name (${name}) must starts from parent name "${this.name}"`);
    }

    const { searchParams, path } = calcUrlProps(url);

    slots = Object.assign({}, this.slots, slots);
    params = Object.assign({}, this.params, params);

    const urlPath = `${this.path.replace(/\/$/, '')}/${path.replace(/^\//, '')}`;
    const urlSearch = `${[ ...this.searchParams, ...searchParams ].join('&')}`;

    url = `${urlPath}${urlSearch ? `?${urlSearch}` : ''}`;

    return new Route(name, { slots, params, url, reroute });
  }
}
<|endoftext|>


<commit_before>// External libraries
import _ from 'lodash';
var fs     = require("fs");
var x11    = require('x11');
var exec   = require('child_process').exec;
var keysym = require('keysym');

// Custom libraries
var conversion = require('./conversion');
var logger     = require('./logger').logger;
//import getWindowProperties from './getWindowProperties.js';
// Load the objects
import Container from './objects/Container.js';
import Frame from './objects/Frame.js';
import Screen from './objects/screen.js';
import Workspace from './objects/Workspace.js';

// The workspaces currently available
let screens = [];
let workspaces = [];

// The available key shortcuts that are known
var config      = require("../config/config");
var programs    = config.startup_applications
var keybindings = config.keybindings
var ks2kc;

function forEachWindow(fn) {
	workspaces.forEach(spc => { fn(spc); });
}

var changeWindowAttributeErrorHandler = function(err) {
	if( err.error === 10 ) {
		logger.error("Another Window Manager is already running, AirWM will now terminate.");
	}
	logger.error(err);
	process.exit(1);
}

var grabKeyBindings = function(ks2kc, display) {
	logger.debug("Grabbing all the keybindings which are configured to have actions in the config.js file.");
	keybindings.forEach(function(keyConfiguration){
		var keyCode = ks2kc[keysym.fromName(keyConfiguration.key).keysym];
		keyConfiguration.mod = 0;
		for( var i in keyConfiguration.modifier ) {
			keyConfiguration.mod = ( keyConfiguration.mod | conversion.translateModifiers(keyConfiguration.modifier[i]) );
		}
		logger.debug("Grabbing key '%s'.", keyCode);
		// Grab the key with each combination of capslock(2), numlock(16) and scrollock (128)
		var combination = [0,2,16,18,128,130,144,146];
		for( var code in combination ) {
			if( (keyConfiguration.mod&combination[code]) !== 0 ) continue;
			global.X.GrabKey(
				display.screen[0].root,
				0, // Don't report events to the window
				keyConfiguration.mod | combination[code],
				keyCode,
				1, // async pointer mode
				1  // async keyboard mode
			);
		}
	});
}

var errorHandler = function(err){
	logger.error(err);
}

var closeAllWindows = function(close_id) {
	logger.info("Closing all windows.");
	var screens = workspaces.getCurrentWorkspace().screens;
	for(var i in screens){
		screens[i].closeAllWindows();
	}
}

// when all windows have been closed, set the focus to the root window
var setFocusToRootIfNecessary = function(){
	var screen = workspaces.getCurrentWorkspace().screens[0];
	if(screen.window_tree.children.length === 0){
		logger.info("Setting focus to the root window");
		global.X.SetInputFocus(screen.root_xid);
	}
}

var commandHandler = function(command) {
	logger.info("Launching airwm-command: '%s'.", command);
	if (!_.isArray(command))
		command = [command];
	switch(command[0]){
		case "Shutdown":
			closeAllWindows();
			process.exit(0);
			break;
		case "CloseWindow":
			if( global.focus_window !== null ) {
				global.focus_window.destroy();
				var w = null;
				forEachWindow(function(window) {
					w = window;
				});
				if( w !== null ) w.focus();

				setFocusToRootIfNecessary();
			}
			break;
		case "SwitchTilingMode":
			if( global.focus_window !== null ) {
				global.focus_window.parent.switchTilingMode();
			}
			break;
		case "MoveWindowLeft":
			if( global.focus_window !== null ) {
				global.focus_window.moveLeft();
			}
			break;
		case "MoveWindowDown":
			if( global.focus_window !== null ) {
				global.focus_window.moveDown();
			}
			break;
		case "MoveWindowUp":
			if( global.focus_window !== null ) {
				global.focus_window.moveUp();
			}
			break;
		case "MoveWindowRight":
			if( global.focus_window !== null ) {
				global.focus_window.moveRight();
			}
			break;
		case "SelectFrameNext":
			console.log("YP")
			if( global.focus_window !== null ) {
				console.log(1);
				const ws = screens[0].workspace.getWindowList();
				console.log(2);
				const i = ws.indexOf(global.focus_window);
				console.log(3);
				if (i >= 0) {
					console.log(3);
					const j = (i + 1) % ws.length;
					ws[j].focus();
				}
			}
			break;
		case "SelectFramePrev":
			if( global.focus_window !== null ) {
				const ws = screens[0].workspace.getWindowList();
				const i = ws.indexOf(global.focus_window);
				if (i >= 0) {
					const j = (i == 0) ? ws.length - 1 : i - 1;
					ws[j].focus();
				}
			}
			break;
		case "SwitchWorkspaceRight":
			workspaces.moveRight();
			break;
		case "SwitchWorkspaceLeft":
			workspaces.moveLeft();
			break;
		default:
			break;
	}
}

var execHandler = function(program) {
	logger.info("Launching external application: '%s'.", program);
	exec(program);
}

var keyPressHandler = function(ev){
	logger.debug("KeyPressHandler is going through all possible keybindings.");
	for(var i = 0; i < keybindings.length; ++i){
		var binding =  keybindings[i];
		// Check if this is the binding which we are seeking.
		if(ks2kc[keysym.fromName(binding.key).keysym] === ev.keycode){
			if( (ev.buttons&(~146)) === binding.mod ){
				if(binding.hasOwnProperty("command")){
					commandHandler(binding.command);
				} else if(binding.hasOwnProperty("program")){
					execHandler(binding.program);
				}
			}
		}
	}
}

var destroyNotifyHandler = function(ev){
	logger.debug("DestroyNotifier got triggered, removing the window that got destroyed.");
	forEachWindow(function(window) {
		if( window.xid === ev.wid ) {
			window.remove();
			if( global.focus_window === window ) {
				var w = null;
				forEachWindow(function(window) {
					w = window;
				});
				if( w !== null ) w.focus();
			}
		}
	});
	setFocusToRootIfNecessary();
}

var mapRequestHandler = function(ev){
	screens[0].addXwin(ev.wid);
}

var eventHandler = function(ev){
	logger.debug("Received a %s event.", ev.name);
	try {
		switch( ev.name ) {
		case "ConfigureRequest":
			// Allow requested resize for optimization. Window gets resized
			// automatically by AirWM again anyway.
			X.ResizeWindow(ev.wid, ev.width, ev.height);
			break
		case "MapRequest":
			mapRequestHandler(ev);
			break;
		case "DestroyNotify":
			destroyNotifyHandler(ev);
			break;
		case "EnterNotify":
			forEachWindow(function(window) {
				if( window.xid === ev.wid ) {
					window.focus();
				}
			});
			break;
		case "KeyPress":
			keyPressHandler(ev);
			break;
		}
	}
	catch (e) {
		logger.error(e);
	}
}

//creates the logDir directory when it doesn't exist (otherwise Winston fails)
var initLogger = function (logDir){
	if(!fs.existsSync(logDir))
		fs.mkdirSync(logDir);
}

var airClientCreator = function(err, display) {
	initLogger('logs');
	logger.info("Initializing AirWM client.");
	// Set the connection to the X server in global namespace
	// as a hack since almost every file uses it
	global.X = display.client;

	// Set the focussed window to null
	global.focus_window = null;

	var min_keycode = display.min_keycode;
	var max_keycode = display.max_keycode;
	X.GetKeyboardMapping(min_keycode, max_keycode-min_keycode, function(err, key_list) {
		ks2kc = conversion.buildKeyMap(key_list,min_keycode);

		// Grab all key combinations which are specified in the configuration file.
		grabKeyBindings(ks2kc,display);
	});

	// Create the workspaces object
	logger.debug("Creating workspaces.");
	//logger.info("display.screen: "+JSON.stringify(display.screen));
	workspaces = _.times(10, () => { return new Workspace(); });
	logger.debug("Creating screens.");
	screens = _.map(display.screen, (screen) => { return new Screen(screen, workspaces); });

	var eventMask = {
		eventMask: x11.eventMask.SubstructureNotify   |
		           x11.eventMask.SubstructureRedirect |
		           x11.eventMask.ResizeRedirect
	}

	// By adding the substructure redirect you become the window manager.
	logger.info("Registering AirWM as the current Window Manager.");
	global.X.ChangeWindowAttributes(display.screen[0].root,eventMask,changeWindowAttributeErrorHandler);

	// Load the programs that should get started and start them
	logger.info("Launching startup applications.");
	programs.forEach(execHandler);
}

x11.createClient(airClientCreator).on('error', errorHandler).on('event', eventHandler);
<commit_msg>Fix focus follows mouse<commit_after>// External libraries
import _ from 'lodash';
var fs     = require("fs");
var x11    = require('x11');
var exec   = require('child_process').exec;
var keysym = require('keysym');

// Custom libraries
var conversion = require('./conversion');
var logger     = require('./logger').logger;
//import getWindowProperties from './getWindowProperties.js';
// Load the objects
import Container from './objects/Container.js';
import Frame from './objects/Frame.js';
import Screen from './objects/screen.js';
import Workspace from './objects/Workspace.js';

// The workspaces currently available
let screens = [];
let workspaces = [];

// The available key shortcuts that are known
var config      = require("../config/config");
var programs    = config.startup_applications
var keybindings = config.keybindings
var ks2kc;

function forEachWindow(fn) {
	//console.log("forEachWindow: "+workspaces)
	workspaces.forEach(spc => { spc.forEachWindow(fn); });
}

var changeWindowAttributeErrorHandler = function(err) {
	if( err.error === 10 ) {
		logger.error("Another Window Manager is already running, AirWM will now terminate.");
	}
	logger.error(err);
	process.exit(1);
}

var grabKeyBindings = function(ks2kc, display) {
	logger.debug("Grabbing all the keybindings which are configured to have actions in the config.js file.");
	keybindings.forEach(function(keyConfiguration){
		var keyCode = ks2kc[keysym.fromName(keyConfiguration.key).keysym];
		keyConfiguration.mod = 0;
		for( var i in keyConfiguration.modifier ) {
			keyConfiguration.mod = ( keyConfiguration.mod | conversion.translateModifiers(keyConfiguration.modifier[i]) );
		}
		logger.debug("Grabbing key '%s'.", keyCode);
		// Grab the key with each combination of capslock(2), numlock(16) and scrollock (128)
		var combination = [0,2,16,18,128,130,144,146];
		for( var code in combination ) {
			if( (keyConfiguration.mod&combination[code]) !== 0 ) continue;
			global.X.GrabKey(
				display.screen[0].root,
				0, // Don't report events to the window
				keyConfiguration.mod | combination[code],
				keyCode,
				1, // async pointer mode
				1  // async keyboard mode
			);
		}
	});
}

var errorHandler = function(err){
	logger.error(err);
}

var closeAllWindows = function(close_id) {
	logger.info("Closing all windows.");
	var screens = workspaces.getCurrentWorkspace().screens;
	for(var i in screens){
		screens[i].closeAllWindows();
	}
}

// when all windows have been closed, set the focus to the root window
var setFocusToRootIfNecessary = function(){
	var screen = workspaces.getCurrentWorkspace().screens[0];
	if(screen.window_tree.children.length === 0){
		logger.info("Setting focus to the root window");
		global.X.SetInputFocus(screen.root_xid);
	}
}

var commandHandler = function(command) {
	logger.info("Launching airwm-command: '%s'.", command);
	if (!_.isArray(command))
		command = [command];
	switch(command[0]){
		case "Shutdown":
			closeAllWindows();
			process.exit(0);
			break;
		case "CloseWindow":
			if( global.focus_window !== null ) {
				global.focus_window.destroy();
				var w = null;
				forEachWindow(function(window) {
					w = window;
				});
				if( w !== null ) w.focus();

				setFocusToRootIfNecessary();
			}
			break;
		case "SwitchTilingMode":
			if( global.focus_window !== null ) {
				global.focus_window.parent.switchTilingMode();
			}
			break;
		case "MoveWindowLeft":
			if( global.focus_window !== null ) {
				global.focus_window.moveLeft();
			}
			break;
		case "MoveWindowDown":
			if( global.focus_window !== null ) {
				global.focus_window.moveDown();
			}
			break;
		case "MoveWindowUp":
			if( global.focus_window !== null ) {
				global.focus_window.moveUp();
			}
			break;
		case "MoveWindowRight":
			if( global.focus_window !== null ) {
				global.focus_window.moveRight();
			}
			break;
		case "SelectFrameNext":
			console.log("YP")
			if( global.focus_window !== null ) {
				console.log(1);
				const ws = screens[0].workspace.getWindowList();
				console.log(2);
				const i = ws.indexOf(global.focus_window);
				console.log(3);
				if (i >= 0) {
					console.log(3);
					const j = (i + 1) % ws.length;
					ws[j].focus();
				}
			}
			break;
		case "SelectFramePrev":
			if( global.focus_window !== null ) {
				const ws = screens[0].workspace.getWindowList();
				const i = ws.indexOf(global.focus_window);
				if (i >= 0) {
					const j = (i == 0) ? ws.length - 1 : i - 1;
					ws[j].focus();
				}
			}
			break;
		case "SwitchWorkspaceRight":
			workspaces.moveRight();
			break;
		case "SwitchWorkspaceLeft":
			workspaces.moveLeft();
			break;
		default:
			break;
	}
}

var execHandler = function(program) {
	logger.info("Launching external application: '%s'.", program);
	exec(program);
}

var keyPressHandler = function(ev){
	logger.debug("KeyPressHandler is going through all possible keybindings.");
	for(var i = 0; i < keybindings.length; ++i){
		var binding =  keybindings[i];
		// Check if this is the binding which we are seeking.
		if(ks2kc[keysym.fromName(binding.key).keysym] === ev.keycode){
			if( (ev.buttons&(~146)) === binding.mod ){
				if(binding.hasOwnProperty("command")){
					commandHandler(binding.command);
				} else if(binding.hasOwnProperty("program")){
					execHandler(binding.program);
				}
			}
		}
	}
}

var destroyNotifyHandler = function(ev){
	logger.debug("DestroyNotifier got triggered, removing the window that got destroyed.");
	forEachWindow(function(window) {
		if( window.xid === ev.wid ) {
			window.remove();
			if( global.focus_window === window ) {
				var w = null;
				forEachWindow(function(window) {
					w = window;
				});
				if( w !== null ) w.focus();
			}
		}
	});
	setFocusToRootIfNecessary();
}

var mapRequestHandler = function(ev){
	screens[0].addXwin(ev.wid);
}

var eventHandler = function(ev){
	logger.info("Received a %s event.", ev.name);
	try {
		switch( ev.name ) {
		case "ConfigureRequest":
			// Allow requested resize for optimization. Window gets resized
			// automatically by AirWM again anyway.
			X.ResizeWindow(ev.wid, ev.width, ev.height);
			break
		case "MapRequest":
			mapRequestHandler(ev);
			break;
		case "DestroyNotify":
			destroyNotifyHandler(ev);
			break;
		case "EnterNotify":
			console.log("EnterNotify")
			forEachWindow(function(window) {
				console.log({xid: window.xid, wid: ev.wid });
				if( window.xid === ev.wid ) {
					window.focus();
				}
			});
			break;
		case "KeyPress":
			keyPressHandler(ev);
			break;
		}
	}
	catch (e) {
		logger.error(e);
	}
}

//creates the logDir directory when it doesn't exist (otherwise Winston fails)
var initLogger = function (logDir){
	if(!fs.existsSync(logDir))
		fs.mkdirSync(logDir);
}

var airClientCreator = function(err, display) {
	initLogger('logs');
	logger.info("Initializing AirWM client.");
	// Set the connection to the X server in global namespace
	// as a hack since almost every file uses it
	global.X = display.client;

	// Set the focussed window to null
	global.focus_window = null;

	var min_keycode = display.min_keycode;
	var max_keycode = display.max_keycode;
	X.GetKeyboardMapping(min_keycode, max_keycode-min_keycode, function(err, key_list) {
		ks2kc = conversion.buildKeyMap(key_list,min_keycode);

		// Grab all key combinations which are specified in the configuration file.
		grabKeyBindings(ks2kc,display);
	});

	// Create the workspaces object
	logger.debug("Creating workspaces.");
	//logger.info("display.screen: "+JSON.stringify(display.screen));
	workspaces = _.times(10, () => { return new Workspace(); });
	logger.debug("Creating screens.");
	screens = _.map(display.screen, (screen) => { return new Screen(screen, workspaces); });

	var eventMask = {
		eventMask: x11.eventMask.SubstructureNotify   |
		           x11.eventMask.SubstructureRedirect |
		           x11.eventMask.ResizeRedirect
	}

	// By adding the substructure redirect you become the window manager.
	logger.info("Registering AirWM as the current Window Manager.");
	global.X.ChangeWindowAttributes(display.screen[0].root,eventMask,changeWindowAttributeErrorHandler);

	// Load the programs that should get started and start them
	logger.info("Launching startup applications.");
	programs.forEach(execHandler);
}

x11.createClient(airClientCreator).on('error', errorHandler).on('event', eventHandler);
<|endoftext|>


<commit_before>import retry from './retry';
import Now from '../lib';

export default class Alias extends Now {
  async ls (url) {
    console.log('list', url);
    const deploymentId = url; // TODO get from API
    return retry(async (bail) => {
      const res = await this._fetch(`/now/aliases/${deploymentId}/`);

      if (200 !== res.status && (400 <= res.status || 500 > res.status)) {
        if (this._debug) console.log('> [debug] bailing on creating due to %s', res.status);
        return bail(responseError(res));
      }

      return await res.json();
    }, { retries: 3, minTimeout: 2500, onRetry: this._onRetry });
  }

  async rm (url, aliases) {
    console.log('rm', url, aliases);
    const deploymentId = url; // TODO get from API
    return await Promise.all(aliases.map(async (alias) => {
      retry(async (bail) => {
        const res = await this._fetch(`/now/aliases/${deploymentId}/${alias}`, {
          method: 'DELETE'
        });

        if (200 !== res.status && (400 <= res.status || 500 > res.status)) {
          if (this._debug) console.log('> [debug] bailing on creating due to %s', res.status);
          return bail(responseError(res));
        }

        return await res.json();
      }, { retries: 3, minTimeout: 2500, onRetry: this._onRetry });
    }));
  }

  async set (url, aliases) {
    console.log('set', url, aliases);
    const deploymentId = url; // TODO get from API
    return retry(async (bail) => {
      const res = await this._fetch(`/now/aliases/${deploymentId}/`, {
        method: 'POST',
        body: {
          aliases: aliases
        }
      });

      if (200 !== res.status && (400 <= res.status || 500 > res.status)) {
        if (this._debug) console.log('> [debug] bailing on creating due to %s', res.status);
        return bail(responseError(res));
      }

      return await res.json();
    }, { retries: 3, minTimeout: 2500, onRetry: this._onRetry });
  }
}

function responseError (res) {
  const err = new Error('Response error');
  err.status = res.status;

  if (429 === res.status) {
    const retryAfter = res.headers.get('Retry-After');
    if (retryAfter) {
      err.retryAfter = parseInt(retryAfter, 10);
    }
  }

  return err;
}
<commit_msg>alias: use global retry<commit_after>import retry from 'async-retry';
import Now from '../lib';

export default class Alias extends Now {
  async ls () {
    return retry(async (bail) => {
      const res = await this._fetch('/now/aliases');

      if (200 !== res.status && (400 <= res.status || 500 > res.status)) {
        if (this._debug) console.log('> [debug] bailing on creating due to %s', res.status);
        return bail(responseError(res));
      }

      return await res.json();
    }, { retries: 3, minTimeout: 2500, onRetry: this._onRetry });
  }

  async rm (url, aliases) {
    console.log('rm', url, aliases);
    const deploymentId = url; // TODO get from API
    return await Promise.all(aliases.map(async (alias) => {
      retry(async (bail) => {
        const res = await this._fetch(`/now/aliases/${deploymentId}/${alias}`, {
          method: 'DELETE'
        });

        if (200 !== res.status && (400 <= res.status || 500 > res.status)) {
          if (this._debug) console.log('> [debug] bailing on creating due to %s', res.status);
          return bail(responseError(res));
        }

        return await res.json();
      }, { retries: 3, minTimeout: 2500, onRetry: this._onRetry });
    }));
  }

  async set (url, aliases) {
    console.log('set', url, aliases);
    const deploymentId = url; // TODO get from API
    return retry(async (bail) => {
      const res = await this._fetch(`/now/aliases/${deploymentId}/`, {
        method: 'POST',
        body: {
          aliases: aliases
        }
      });

      if (200 !== res.status && (400 <= res.status || 500 > res.status)) {
        if (this._debug) console.log('> [debug] bailing on creating due to %s', res.status);
        return bail(responseError(res));
      }

      return await res.json();
    }, { retries: 3, minTimeout: 2500, onRetry: this._onRetry });
  }
}

function responseError (res) {
  const err = new Error('Response error');
  err.status = res.status;

  if (429 === res.status) {
    const retryAfter = res.headers.get('Retry-After');
    if (retryAfter) {
      err.retryAfter = parseInt(retryAfter, 10);
    }
  }

  return err;
}
<|endoftext|>


<commit_before>/**
 * archr - Search for and download Arch Linux packages
 * https://github.com/gavinhungry/archr
 */

(function() {
  'use strict';

  var archr = module.exports;

  var Q = require('q');
  var request = require('request');
  var sprintf = require('sprintf');

  // Arch Linux JSON API
  var ARCH_URI = 'https://www.archlinux.org/packages';
  var PKG_URI = ARCH_URI + '/%s/%s/%s/json/'; // repo/arch/pkgname
  var SEARCH_URI = ARCH_URI + '/search/json/?arch=any&arch=%s&q=%s'; // &repo=
  var ANY_ARCH = 'i686';

  archr.defaults = {
    repo: 'core',
    arch: 'x86_64',

    // same format as /etc/pacman.d/mirrorlist
    mirror: 'https://mirrors.kernel.org/archlinux/$repo/os/$arch'
  };

  /**
   * Capitalize dash seperated strings
   *
   * @example
   * caps('foo-bar'); // "Foo-Bar"
   *
   * @param {String} str - input string
   * @return {String} str with capitalized words
   */
  var caps = function(str) {
    return str.split('-').map(function(sub) {
      return sub[0].toUpperCase() + sub.slice(1);
    }).join('-');
  };

  /**
   * Get remote JSON
   *
   * @param {String} uri
   * @return {Promise} to resolve to parsed Object, or reject with error
   */
  var json = function(uri) {
    var d = Q.defer();

    request(uri, function(error, response, data) {
      if (error || response.statusCode !== 200) {
        return d.reject(error);
      }

      var obj;

      try {
        obj = JSON.parse(data);
      } catch(err) {
        return d.reject(err);
      }

      d.resolve(obj);
    });

    return d.promise;
  };

  /**
   * Search for packages
   *
   * @param {String} query - package query
   * @param {String} [repo] - package repository name
   * @param {String} [arch] - system architecture
   * @return {Promise} to resolve to results, or reject with error
   */
  archr.search = function(query, repo, arch) {
    arch = arch || archr.defaults.arch;

    var uri = sprintf(SEARCH_URI, arch, query);
    if (repo) { uri += '&repo=' + caps(repo); }

    return json(uri).then(function(obj) {
      return obj.results;
    });
  };

  /**
   * Get data for a single package
   *
   * @param {String} pkgname - package name
   * @param {String} [repo] - package repository name
   * @param {String} [arch] - system architecture
   * @return {Promise} to resolve to result, or reject with error
   */
  archr.package = function(pkgname, repo, arch) {
    repo = repo || archr.defaults.repo;
    arch = arch || archr.defaults.arch;

    var uri = sprintf(PKG_URI, caps(repo), arch, pkgname);
    return json(uri).fail(function() {
      if (arch !== 'any') {
        uri = sprintf(PKG_URI, caps(repo), 'any', pkgname);
        return json(uri);
      }
    });
  };

  /**
   * Get a download URI
   *
   * @param {String} pkgname - package name
   * @return {Promise} to resolve to download URI, or reject with error
   */
  archr.download = function(pkgname, repo, arch) {
    return archr.package(pkgname, repo, arch).then(archr.uri);
  };

  /**
   * Get a download URI from a package object
   *
   * @param {Object} pkg - package object
   * @return {String} download URI
   */
  archr.uri = function(pkg) {
    return archr.defaults.mirror
      .replace('$repo', pkg.repo)
      .replace('$arch', pkg.arch === 'any' ? ANY_ARCH : pkg.arch) +
      '/' + pkg.filename;
  };

})();
<commit_msg>Try to read mirror from /etc/pacman.d/mirrorlist<commit_after>/**
 * archr - Search for and download Arch Linux packages
 * https://github.com/gavinhungry/archr
 */

(function() {
  'use strict';

  var archr = module.exports;

  var fs = require('fs');
  var Q = require('q');
  var request = require('request');
  var sprintf = require('sprintf');

  // Arch Linux JSON API
  var ANY_ARCH = 'i686';
  var ARCH_URI = 'https://www.archlinux.org/packages';
  var MIRRORLIST = '/etc/pacman.d/mirrorlist';
  var PKG_URI = ARCH_URI + '/%s/%s/%s/json/'; // repo/arch/pkgname
  var SEARCH_URI = ARCH_URI + '/search/json/?arch=any&arch=%s&q=%s'; // &repo=

  archr.defaults = {
    repo: 'core',
    arch: 'x86_64'
  };

  try {
    var mirrorlist = fs.readFileSync(MIRRORLIST).toString();

    var match = mirrorlist.match(new RegExp(/^Server\s*=\s*(.*)$/m));
    archr.defaults.mirror = match[1];
  } catch(e) {
    archr.defaults.mirror = 'https://mirrors.kernel.org/archlinux/$repo/os/$arch';
  }

  /**
   * Capitalize dash seperated strings
   *
   * @example
   * caps('foo-bar'); // "Foo-Bar"
   *
   * @param {String} str - input string
   * @return {String} str with capitalized words
   */
  var caps = function(str) {
    return str.split('-').map(function(sub) {
      return sub[0].toUpperCase() + sub.slice(1);
    }).join('-');
  };

  /**
   * Get remote JSON
   *
   * @param {String} uri
   * @return {Promise} to resolve to parsed Object, or reject with error
   */
  var json = function(uri) {
    var d = Q.defer();

    request(uri, function(error, response, data) {
      if (error || response.statusCode !== 200) {
        return d.reject(error);
      }

      var obj;

      try {
        obj = JSON.parse(data);
      } catch(err) {
        return d.reject(err);
      }

      d.resolve(obj);
    });

    return d.promise;
  };

  /**
   * Search for packages
   *
   * @param {String} query - package query
   * @param {String} [repo] - package repository name
   * @param {String} [arch] - system architecture
   * @return {Promise} to resolve to results, or reject with error
   */
  archr.search = function(query, repo, arch) {
    arch = arch || archr.defaults.arch;

    var uri = sprintf(SEARCH_URI, arch, query);
    if (repo) { uri += '&repo=' + caps(repo); }

    return json(uri).then(function(obj) {
      return obj.results;
    });
  };

  /**
   * Get data for a single package
   *
   * @param {String} pkgname - package name
   * @param {String} [repo] - package repository name
   * @param {String} [arch] - system architecture
   * @return {Promise} to resolve to result, or reject with error
   */
  archr.package = function(pkgname, repo, arch) {
    repo = repo || archr.defaults.repo;
    arch = arch || archr.defaults.arch;

    var uri = sprintf(PKG_URI, caps(repo), arch, pkgname);
    return json(uri).fail(function() {
      if (arch !== 'any') {
        uri = sprintf(PKG_URI, caps(repo), 'any', pkgname);
        return json(uri);
      }
    });
  };

  /**
   * Get a download URI
   *
   * @param {String} pkgname - package name
   * @return {Promise} to resolve to download URI, or reject with error
   */
  archr.download = function(pkgname, repo, arch) {
    return archr.package(pkgname, repo, arch).then(archr.uri);
  };

  /**
   * Get a download URI from a package object
   *
   * @param {Object} pkg - package object
   * @return {String} download URI
   */
  archr.uri = function(pkg) {
    return archr.defaults.mirror
      .replace('$repo', pkg.repo)
      .replace('$arch', pkg.arch === 'any' ? ANY_ARCH : pkg.arch) +
      '/' + pkg.filename;
  };

})();
<|endoftext|>


<commit_before>var http = require('http'),
    zlib = require('zlib'),
    url = require('url'),
    extend = require('extend'),
    AdvancedAgent = require('./advanced_agent'),
    AskerError = require('./error');

/**
 * @typedef {Object} AgentOptions
 * @property {String}  [name=globalAgent] Agent name, use 'globalAgent' for http.globalAgent
 * @property {Number}  [maxSockets=1024] Pool size, used only if new agent defined
 * @property {Boolean} [persistent=true] Non-persistent agents removed when queue empty
 */

/**
 * @typedef  {Object} RequestOptions
 * @property {String}   [url] shorthand alternative for host, port and path options
 * @property {String}   [host=localhost]
 * @property {Number}   [port=80]
 * @property {String}   [path=/]
 * @property {String}   [method=GET] HTTP-method
 * @property {Object}   [headers] HTTP headers hash
 * @property {Object}   [query] Query params hash
 * @property {String}   [requestId=''] Request identifier for error messages
 * @property {*}        [body] Request body for POST, PUT and PATCH methods, JSON.stringify applies if Object
 * @property {Number}   [maxRetries=0] Max number of allowed retries for request
 * @property {Function} [onretry] (reason Error, retryCount Number) called on retries
 * @property {Number}   [timeout=500] Socket timeout
 * @property {Number}   [queueTimeout=timeout+50] Queue timeout
 * @property {Boolean}  [allowGzip=true] Allows response compression with gzip
 * @property {Function} [statusFilter] (code Number) Filter which determines acceptable status codes
 *      by default only 200 and 201 codes acceptable and retries allowed for all codes, except range from 400 to 499.
 *      Must returns object { accept : Boolean, isRetryAllowed : Boolean }.
 * @property {AgentOptions} [agent] http.Agent options
 */

/**
 * Create new instance and calls the `execute()` method if called as function (without `new`).
 *
 * @constructor
 * @param {RequestOptions} options object with only `host` field required
 * @param {Function} callback
 */
function Request(options, callback) {
    var parsedUrl,
        parsedPath,
        acceptEncoding;

    if ( ! (this instanceof Request)) {
        return (new Request(options, callback)).execute();
    }

    this.options = extend({}, Request.DEFAULT_OPTIONS, options);

    this._callback = callback;
    this._onretry = this.options.onretry;

    if (typeof this.options.statusFilter === 'function') {
        this.statusCodeFilter = this.options.statusFilter;
    }

    // lowercase headers names
    this.options.headers = (options && options.headers) ?
        Object.keys(this.options.headers).reduce(function(headers, headerName) {
            headers[headerName.toLowerCase()] = options.headers[headerName];

            return headers;
        }, {}) :
        {};

    if (this.options.url) {
        parsedUrl = url.parse(this.options.url, true);
        this.options.host = parsedUrl.hostname;
        this.options.port = parsedUrl.port;
        this.options.path = parsedUrl.path;
    }

    // rebuild path with query params
    // `query` hash properties has higher priority than the specified in the `path` string does
    if (this.options.query) {
        parsedPath = url.parse(this.options.path, true);

        // `search` prop has higher priority than the `query` then remove it
        delete parsedPath.search;
        extend(parsedPath.query, this.options.query);

        this.options.path = url.format(parsedPath);
    }

    // lets build request body for POST, PUT and PATCH methods
    if (['POST','PUT','PATCH'].indexOf(this.options.method) > -1 && typeof this.options.body !== 'undefined') {
        this.options.body = (typeof this.options.body === 'object') ?
            JSON.stringify(this.options.body) :
            String(this.options.body);

        // @todo does we must support encodings rather than 'utf8'?
        if ( ! this.options.headers['content-length']) {
            this.options.headers['content-length'] = Buffer.byteLength(this.options.body, 'utf8');
        }
    }

    if (this.options.allowGzip) {
        acceptEncoding = this.options.headers['accept-encoding'] || 'gzip, *';

        if (acceptEncoding.indexOf('gzip') === -1) {
            this.options.headers['accept-encoding'] = 'gzip, ' + acceptEncoding;
        }
    }

    this.agent = Request.getAgent(this);
}

Request.Error = AskerError;

// default Request options
Request.DEFAULT_OPTIONS = {
    host : 'localhost',
    port : 80,
    path : '/',
    method : 'GET',
    maxRetries : 0,
    timeout : 500,
    allowGzip : true,
    requestId : ''
};

// if not defined queueTimeout = timeout + QUEUE_TIMEOUT_DELTA
Request.prototype.QUEUE_TIMEOUT_DELTA = 50;

// pool of Agents. Yes, it's pool of pools
Request.agentsPool = {};

http.globalAgent.maxSockets = 1024;
http.Agent.defaultMaxSockets = 1024;

/**
 * @param {AgentOptions} options
 * @returns {AdvancedAgent}
 */
Request.createAgent = function(options) {
    var agentsPool = this.agentsPool,
        agent = new AdvancedAgent(extend({ persistent : true }, options));

    agentsPool[agent.options.name] = agent;

    // Setup `removeSocket` event listener for non-persistnt agents.
    // Destroy agent then requests queue and sockets pool became empty.
    if ( ! agent.options.persistent) {
        agent.on(AdvancedAgent.EVENTS.SOCKET_REMOVED, function() {
            if (this.requests.keys().length === 0 && this.sockets.keys().length === 0) {
                delete agentsPool[agent.options.name];
            }
        });
    }

    return agent;
};

/**
 * Returns existing agent from agents pool or creates new if no one exists
 * @param {Request} request instance
 * @returns {Agent|AdvancedAgent}
 */
Request.getAgent = function(request) {
    var options = request.options.agent,
        agent = false;

    // returns `http.globalAgent` if agent name is undefined or equals 'globalAgent'
    if ( ! options || ! options.name || options.name === 'globalAgent') {
        agent = http.globalAgent;
    } else if (Object.prototype.hasOwnProperty.call(this.agentsPool, options.name)) {
        agent = this.agentsPool[options.name];
    }

    return agent || this.createAgent(options);
};

/**
 * Accept or decline status code, allows or decline retries
 * @param {Number} code
 * @returns {{accept : Boolean, isRetryAllowed : Boolean}}
 */
Request.prototype.statusCodeFilter = function(code) {
    return {
        accept : code === 200 || code === 201,
        isRetryAllowed : 400 > code || code > 499
    };
};

/**
 * @returns {{network: (undefined|number), total: (undefined|number)}}
 */
Request.prototype.getTimers = function() {
    var now = Date.now();

    return {
        network : this._timeNetworkStart && ((this._timeNetworkEnd || now) - this._timeNetworkStart),
        total : this._timeExecuteStart && (now - this._timeExecuteStart)
    };
};

/**
 * timestamp for error messages in the '[in XX~YY ms]' format
 * XX – time between socket was assigned for request and response or error retrieved
 * YY – time about `execute` method call and response or error retrieved
 * @returns {String}
 */
Request.prototype.formatTimestamp = function() {
    var timers = this.getTimers();

    return ['in ', timers.network || '0', '~', timers.total, ' ms'].join('');
};

/**
 * @returns {{time:{network:number,total:number},retries:{used:number,limit:number}}}
 */
Request.prototype.getResponseMetaBase = function() {
    return {
        time : this.getTimers(),
        options : this.options,
        retries : {
            used : this.retries,
            limit : this.maxRetries
        }
    };
};

/**
 * @memberOf Request.prototype
 * @field {boolean} isRunning
 */
Object.defineProperty(Request.prototype, 'isRunning', {
    get : function() {
        return !! this._isRunning;
    },
    enumerable : true
});

/**
 * @param {Error} err
 * @param {Object} [data]
 * @returns {*}
 */
Request.prototype.done = function(err, data) {
    this._isRunning = false;

    return this._callback(err, data);
};

/**
 * Successfully resolve request
 * @param {*} response data
 * @param {Object} [meta]
 */
Request.prototype.resolve = function(response, meta) {
    this.done(null, {
        data : response,
        meta : meta ? extend(this.getResponseMetaBase(), meta) : this.getResponseMetaBase()
    });
};

/**
 * Handle request retries. Throw an error if retries limit exceeded.
 * @param {Object} requestOptions same as for _tryHttpRequest
 * @param {AskerError} retryReason error which is a reason for retry
 * @private
 */
Request.prototype._retryHttpRequest = function(requestOptions, retryReason) {
    var errorData = {
            requestId : this.options.requestId,
            timings : this.formatTimestamp(),
            maxRetries : this.options.maxRetries,
            url : [
                requestOptions.host,
                requestOptions.port ? ':' + requestOptions.port : '',
                requestOptions.path
            ].join('')
        };

    retryReason.bind(errorData);

    if (this.retries >= this.options.maxRetries) {
        // retries limit exceeded
        // throw an RETRIES_LIMIT_EXCEEDED if retries allowed for request
        // or retry reason error in another case
        if (this.options.maxRetries > 0) {
            // @todo throw following error with `reason` prop which contains retryReason
            // so user code can determine limits exceeded errors
            retryReason = AskerError
                .createError(AskerError.CODES.RETRIES_LIMIT_EXCEEDED, retryReason)
                .bind(errorData);
        }
        this.done(retryReason);
    } else {
        this.retries++;
        this._onretry && this._onretry(retryReason, this.retries);
        this._tryHttpRequest(requestOptions);
    }
};

/**
 * run the request
 * @param options Параметры запроса, аналогичны параметрам http.request
 * @private
 */
Request.prototype._tryHttpRequest = function(options) {
    var self = this,
        url = options.host + ':' + options.port + options.path,
        retryRequest = this._retryHttpRequest.bind(this, options),
        httpRequest,
        statusFilterResult;

    httpRequest = http.request(options, function(res) {
        var contentLen = parseInt(res.headers['content-length'], 10),
            body = contentLen && new Buffer(contentLen),
            offset = 0,
            error;

        contentLen && body.fill(0);

        statusFilterResult = self.statusCodeFilter(res.statusCode);

        if ( ! statusFilterResult.accept) {
            httpRequest.break(true);
            error = AskerError.createError(
                AskerError.CODES.UNEXPECTED_STATUS_CODE, { statusCode : res.statusCode, url : url });
            return statusFilterResult.isRetryAllowed ? retryRequest(error) : self.done(error);
        }

        res.on('data', function(chunk) {
            var isBuffer = Buffer.isBuffer(chunk),
                chunkBuffer;

            httpRequest.clearTimeouts();

            if (contentLen) {
                offset += isBuffer ? chunk.copy(body, offset) : body.write(chunk, offset);
            } else {
                chunkBuffer = isBuffer ? chunk : new Buffer(chunk);
                body = body ? Buffer.concat([body, chunkBuffer]) : chunkBuffer;
            }
        });

        res.on('end', function() {
            var encoding = res.headers['content-encoding'];

            // for sure ;)
            httpRequest.clearTimeouts();

            if (encoding && encoding.toLowerCase().indexOf('gzip') > -1) {
                zlib.gunzip(body, function(err, deflatedBody) {
                    error ?
                        self.done(AskerError.createError(AskerError.CODES.GUNZIP_ERROR, error)) :
                        self.resolve(deflatedBody.toString(), { gzip : true });
                });
            } else {
                self.resolve(body.toString());
            }
        });
    });

    httpRequest.clearTimeouts = function() {
        httpRequest.socketTimeout && clearTimeout(httpRequest.socketTimeout);
        httpRequest.queueTimeout && clearTimeout(httpRequest.queueTimeout);
    };

    httpRequest.break = function(abort, errorCode, errorData) {
        httpRequest.clearTimeouts();

        if (abort) {
            httpRequest.rejected = true;
            httpRequest.abort();
        }

        httpRequest.emit('removeSocket');
        self._timeNetworkEnd = Date.now();

        errorCode && retryRequest(AskerError.createError(errorCode, errorData));
    };

    // setup queue timeout
    httpRequest.queueTimeout = setTimeout(function() {
        httpRequest.break(true, AskerError.CODES.QUEUE_TIMEOUT);
    }, options.timeout + self.QUEUE_TIMEOUT_DELTA);

    httpRequest.on('socket', function() {
        self._timeNetworkStart = Date.now();

        httpRequest.socketTimeout = setTimeout(function() {
            httpRequest.break(true, AskerError.CODES.SOCKET_TIMEOUT);
        }, options.timeout);
    });

    httpRequest.on('error', function(error) {
        // request manually aborted
        ! httpRequest.rejected && httpRequest.break(false, AskerError.CODES.HTTP_CLIENT_REQUEST_ERROR, error);
    });

    // send request body
    this.options.body && httpRequest.write(this.options.body);

    httpRequest.end();
};

/**
 * Execute request
 * @returns {boolean} `false` if request already running
 */
Request.prototype.execute = function() {
    if (this.isRunning) {
        return false;
    }

    this.retries = 0;
    this._timeExecuteStart = Date.now();
    this._tryHttpRequest(extend({}, this.options, { agent : this.agent }));

    return true;
};

module.exports = Request;
<commit_msg>fix "gzip" absence in accept-encoding header if no headers passed to ctor #10<commit_after>var http = require('http'),
    zlib = require('zlib'),
    url = require('url'),
    extend = require('extend'),
    AdvancedAgent = require('./advanced_agent'),
    AskerError = require('./error');

/**
 * @typedef {Object} AgentOptions
 * @property {String}  [name=globalAgent] Agent name, use 'globalAgent' for http.globalAgent
 * @property {Number}  [maxSockets=1024] Pool size, used only if new agent defined
 * @property {Boolean} [persistent=true] Non-persistent agents removed when queue empty
 */

/**
 * @typedef  {Object} RequestOptions
 * @property {String}   [url] shorthand alternative for host, port and path options
 * @property {String}   [host=localhost]
 * @property {Number}   [port=80]
 * @property {String}   [path=/]
 * @property {String}   [method=GET] HTTP-method
 * @property {Object}   [headers] HTTP headers hash
 * @property {Object}   [query] Query params hash
 * @property {String}   [requestId=''] Request identifier for error messages
 * @property {*}        [body] Request body for POST, PUT and PATCH methods, JSON.stringify applies if Object
 * @property {Number}   [maxRetries=0] Max number of allowed retries for request
 * @property {Function} [onretry] (reason Error, retryCount Number) called on retries
 * @property {Number}   [timeout=500] Socket timeout
 * @property {Number}   [queueTimeout=timeout+50] Queue timeout
 * @property {Boolean}  [allowGzip=true] Allows response compression with gzip
 * @property {Function} [statusFilter] (code Number) Filter which determines acceptable status codes
 *      by default only 200 and 201 codes acceptable and retries allowed for all codes, except range from 400 to 499.
 *      Must returns object { accept : Boolean, isRetryAllowed : Boolean }.
 * @property {AgentOptions} [agent] http.Agent options
 */

/**
 * Create new instance and calls the `execute()` method if called as function (without `new`).
 *
 * @constructor
 * @param {RequestOptions} options object with only `host` field required
 * @param {Function} callback
 */
function Request(options, callback) {
    var parsedUrl,
        parsedPath,
        acceptEncoding;

    if ( ! (this instanceof Request)) {
        return (new Request(options, callback)).execute();
    }

    this.options = extend({}, Request.DEFAULT_OPTIONS, options);

    this._callback = callback;
    this._onretry = this.options.onretry;

    if (typeof this.options.statusFilter === 'function') {
        this.statusCodeFilter = this.options.statusFilter;
    }

    // lowercase headers names
    this.options.headers = (options && options.headers) ?
        Object.keys(this.options.headers).reduce(function(headers, headerName) {
            headers[headerName.toLowerCase()] = options.headers[headerName];

            return headers;
        }, {}) :
        {};

    if (this.options.url) {
        parsedUrl = url.parse(this.options.url, true);
        this.options.host = parsedUrl.hostname;
        this.options.port = parsedUrl.port;
        this.options.path = parsedUrl.path;
    }

    // rebuild path with query params
    // `query` hash properties has higher priority than the specified in the `path` string does
    if (this.options.query) {
        parsedPath = url.parse(this.options.path, true);

        // `search` prop has higher priority than the `query` then remove it
        delete parsedPath.search;
        extend(parsedPath.query, this.options.query);

        this.options.path = url.format(parsedPath);
    }

    // lets build request body for POST, PUT and PATCH methods
    if (['POST','PUT','PATCH'].indexOf(this.options.method) > -1 && typeof this.options.body !== 'undefined') {
        this.options.body = (typeof this.options.body === 'object') ?
            JSON.stringify(this.options.body) :
            String(this.options.body);

        // @todo does we must support encodings rather than 'utf8'?
        if ( ! this.options.headers['content-length']) {
            this.options.headers['content-length'] = Buffer.byteLength(this.options.body, 'utf8');
        }
    }

    if (this.options.allowGzip) {
        acceptEncoding = this.options.headers['accept-encoding'] || '*';

        if (acceptEncoding.indexOf('gzip') === -1) {
            this.options.headers['accept-encoding'] = 'gzip, ' + acceptEncoding;
        }
    }

    this.agent = Request.getAgent(this);
}

Request.Error = AskerError;

// default Request options
Request.DEFAULT_OPTIONS = {
    host : 'localhost',
    port : 80,
    path : '/',
    method : 'GET',
    maxRetries : 0,
    timeout : 500,
    allowGzip : true,
    requestId : ''
};

// if not defined queueTimeout = timeout + QUEUE_TIMEOUT_DELTA
Request.prototype.QUEUE_TIMEOUT_DELTA = 50;

// pool of Agents. Yes, it's pool of pools
Request.agentsPool = {};

http.globalAgent.maxSockets = 1024;
http.Agent.defaultMaxSockets = 1024;

/**
 * @param {AgentOptions} options
 * @returns {AdvancedAgent}
 */
Request.createAgent = function(options) {
    var agentsPool = this.agentsPool,
        agent = new AdvancedAgent(extend({ persistent : true }, options));

    agentsPool[agent.options.name] = agent;

    // Setup `removeSocket` event listener for non-persistnt agents.
    // Destroy agent then requests queue and sockets pool became empty.
    if ( ! agent.options.persistent) {
        agent.on(AdvancedAgent.EVENTS.SOCKET_REMOVED, function() {
            if (this.requests.keys().length === 0 && this.sockets.keys().length === 0) {
                delete agentsPool[agent.options.name];
            }
        });
    }

    return agent;
};

/**
 * Returns existing agent from agents pool or creates new if no one exists
 * @param {Request} request instance
 * @returns {Agent|AdvancedAgent}
 */
Request.getAgent = function(request) {
    var options = request.options.agent,
        agent = false;

    // returns `http.globalAgent` if agent name is undefined or equals 'globalAgent'
    if ( ! options || ! options.name || options.name === 'globalAgent') {
        agent = http.globalAgent;
    } else if (Object.prototype.hasOwnProperty.call(this.agentsPool, options.name)) {
        agent = this.agentsPool[options.name];
    }

    return agent || this.createAgent(options);
};

/**
 * Accept or decline status code, allows or decline retries
 * @param {Number} code
 * @returns {{accept : Boolean, isRetryAllowed : Boolean}}
 */
Request.prototype.statusCodeFilter = function(code) {
    return {
        accept : code === 200 || code === 201,
        isRetryAllowed : 400 > code || code > 499
    };
};

/**
 * @returns {{network: (undefined|number), total: (undefined|number)}}
 */
Request.prototype.getTimers = function() {
    var now = Date.now();

    return {
        network : this._timeNetworkStart && ((this._timeNetworkEnd || now) - this._timeNetworkStart),
        total : this._timeExecuteStart && (now - this._timeExecuteStart)
    };
};

/**
 * timestamp for error messages in the '[in XX~YY ms]' format
 * XX – time between socket was assigned for request and response or error retrieved
 * YY – time about `execute` method call and response or error retrieved
 * @returns {String}
 */
Request.prototype.formatTimestamp = function() {
    var timers = this.getTimers();

    return ['in ', timers.network || '0', '~', timers.total, ' ms'].join('');
};

/**
 * @returns {{time:{network:number,total:number},retries:{used:number,limit:number}}}
 */
Request.prototype.getResponseMetaBase = function() {
    return {
        time : this.getTimers(),
        options : this.options,
        retries : {
            used : this.retries,
            limit : this.maxRetries
        }
    };
};

/**
 * @memberOf Request.prototype
 * @field {boolean} isRunning
 */
Object.defineProperty(Request.prototype, 'isRunning', {
    get : function() {
        return !! this._isRunning;
    },
    enumerable : true
});

/**
 * @param {Error} err
 * @param {Object} [data]
 * @returns {*}
 */
Request.prototype.done = function(err, data) {
    this._isRunning = false;

    return this._callback(err, data);
};

/**
 * Successfully resolve request
 * @param {*} response data
 * @param {Object} [meta]
 */
Request.prototype.resolve = function(response, meta) {
    this.done(null, {
        data : response,
        meta : meta ? extend(this.getResponseMetaBase(), meta) : this.getResponseMetaBase()
    });
};

/**
 * Handle request retries. Throw an error if retries limit exceeded.
 * @param {Object} requestOptions same as for _tryHttpRequest
 * @param {AskerError} retryReason error which is a reason for retry
 * @private
 */
Request.prototype._retryHttpRequest = function(requestOptions, retryReason) {
    var errorData = {
            requestId : this.options.requestId,
            timings : this.formatTimestamp(),
            maxRetries : this.options.maxRetries,
            url : [
                requestOptions.host,
                requestOptions.port ? ':' + requestOptions.port : '',
                requestOptions.path
            ].join('')
        };

    retryReason.bind(errorData);

    if (this.retries >= this.options.maxRetries) {
        // retries limit exceeded
        // throw an RETRIES_LIMIT_EXCEEDED if retries allowed for request
        // or retry reason error in another case
        if (this.options.maxRetries > 0) {
            // @todo throw following error with `reason` prop which contains retryReason
            // so user code can determine limits exceeded errors
            retryReason = AskerError
                .createError(AskerError.CODES.RETRIES_LIMIT_EXCEEDED, retryReason)
                .bind(errorData);
        }
        this.done(retryReason);
    } else {
        this.retries++;
        this._onretry && this._onretry(retryReason, this.retries);
        this._tryHttpRequest(requestOptions);
    }
};

/**
 * run the request
 * @param options Параметры запроса, аналогичны параметрам http.request
 * @private
 */
Request.prototype._tryHttpRequest = function(options) {
    var self = this,
        url = options.host + ':' + options.port + options.path,
        retryRequest = this._retryHttpRequest.bind(this, options),
        httpRequest,
        statusFilterResult;

    httpRequest = http.request(options, function(res) {
        var contentLen = parseInt(res.headers['content-length'], 10),
            body = contentLen && new Buffer(contentLen),
            offset = 0,
            error;

        contentLen && body.fill(0);

        statusFilterResult = self.statusCodeFilter(res.statusCode);

        if ( ! statusFilterResult.accept) {
            httpRequest.break(true);
            error = AskerError.createError(
                AskerError.CODES.UNEXPECTED_STATUS_CODE, { statusCode : res.statusCode, url : url });
            return statusFilterResult.isRetryAllowed ? retryRequest(error) : self.done(error);
        }

        res.on('data', function(chunk) {
            var isBuffer = Buffer.isBuffer(chunk),
                chunkBuffer;

            httpRequest.clearTimeouts();

            if (contentLen) {
                offset += isBuffer ? chunk.copy(body, offset) : body.write(chunk, offset);
            } else {
                chunkBuffer = isBuffer ? chunk : new Buffer(chunk);
                body = body ? Buffer.concat([body, chunkBuffer]) : chunkBuffer;
            }
        });

        res.on('end', function() {
            var encoding = res.headers['content-encoding'];

            // for sure ;)
            httpRequest.clearTimeouts();

            if (encoding && encoding.toLowerCase().indexOf('gzip') > -1) {
                zlib.gunzip(body, function(err, deflatedBody) {
                    error ?
                        self.done(AskerError.createError(AskerError.CODES.GUNZIP_ERROR, error)) :
                        self.resolve(deflatedBody.toString(), { gzip : true });
                });
            } else {
                self.resolve(body.toString());
            }
        });
    });

    httpRequest.clearTimeouts = function() {
        httpRequest.socketTimeout && clearTimeout(httpRequest.socketTimeout);
        httpRequest.queueTimeout && clearTimeout(httpRequest.queueTimeout);
    };

    httpRequest.break = function(abort, errorCode, errorData) {
        httpRequest.clearTimeouts();

        if (abort) {
            httpRequest.rejected = true;
            httpRequest.abort();
        }

        httpRequest.emit('removeSocket');
        self._timeNetworkEnd = Date.now();

        errorCode && retryRequest(AskerError.createError(errorCode, errorData));
    };

    // setup queue timeout
    httpRequest.queueTimeout = setTimeout(function() {
        httpRequest.break(true, AskerError.CODES.QUEUE_TIMEOUT);
    }, options.timeout + self.QUEUE_TIMEOUT_DELTA);

    httpRequest.on('socket', function() {
        self._timeNetworkStart = Date.now();

        httpRequest.socketTimeout = setTimeout(function() {
            httpRequest.break(true, AskerError.CODES.SOCKET_TIMEOUT);
        }, options.timeout);
    });

    httpRequest.on('error', function(error) {
        // request manually aborted
        ! httpRequest.rejected && httpRequest.break(false, AskerError.CODES.HTTP_CLIENT_REQUEST_ERROR, error);
    });

    // send request body
    this.options.body && httpRequest.write(this.options.body);

    httpRequest.end();
};

/**
 * Execute request
 * @returns {boolean} `false` if request already running
 */
Request.prototype.execute = function() {
    if (this.isRunning) {
        return false;
    }

    this.retries = 0;
    this._timeExecuteStart = Date.now();
    this._tryHttpRequest(extend({}, this.options, { agent : this.agent }));

    return true;
};

module.exports = Request;
<|endoftext|>


<commit_before>
function reduce (treatments) {

  var results = [ ];

  var state = { };
  var previous = [ ];

  function in_previous (ev) {
    var found = false;
    previous.forEach(function (elem) {
      if (elem.timestamp == ev.timestamp && ev._type == elem._type) {
        found = true;
      }
    });

    return found;
  }

  function within_minutes_from (origin, tail, minutes) {
    var ms = minutes * 1000 * 60;
    var ts = Date.parse(origin.timestamp);
    var candidates = tail.slice( ).filter(function (elem) {
      var dt = Date.parse(origin.timestamp);
      return ts - dt <= ms;

    });
    return candidates;
  }

  function bolus (ev, remaining) {
    if (!ev) { console.error('XXX', ev, remaining); return; }
    if (ev._type == 'BolusWizard') {
      state.carbs = ev.carb_input.toString( );
      state.ratio = ev.carb_ratio.toString( );
      state.bg = ev.bg.toString( );
      state.glucose = ev.bg.toString( );
      state.glucoseType = ev._type;
      state.wizard = ev;
      state.created_at = state.timestamp = ev.timestamp;
      previous.push(ev);
    }

    if (ev._type == 'Bolus') {
      state.duration = ev.duration.toString( );
      // if (state.square || state.bolus) { }
      // state.insulin = (state.insulin ? state.insulin : 0) + ev.amount;
      if (ev.duration && ev.duration > 0) {
        state.square = ev;
      } else {
        state.bolus = ev;
      }
      state.created_at = state.timestamp = ev.timestamp;
      previous.push(ev);
    }

    if (remaining && remaining.length > 0) {
      if (state.bolus && state.wizard) {
        // skip to end
        return bolus({}, []);
      }
      // keep recursing
      return bolus(remaining[0], remaining.slice(1));
    } else {
      // console.error("state", state);
      // console.error("remaining", remaining);
      state.eventType = '<none>';

      state.insulin = (state.square ? state.square.amount : 0) +
                      (state.bolus ? state.bolus.amount : 0);
      var has_insulin = state.insulin && state.insulin > 0;
      var has_carbs = state.carbs && state.carbs > 0;
      var has_wizard = state.wizard ? true : false;
      var has_bolus = state.bolus ? true : false;
      if (state.square && state.bolus) {
        annotate("DualWave bolus for", state.square.duration, "minutes");
      } else if (state.square && state.wizard) {
        annotate("Square wave bolus for", state.square.duration, "minutes");
      } else if (state.square) {
        annotate("Solo Square wave bolus for", state.square.duration, "minutes");
        annotate("No bolus wizard used.");
      } else if (state.bolus && state.wizard) {
        annotate("Normal bolus with wizard.");
      } else if (state.bolus) {
        annotate("Normal bolus (solo, no bolus wizard).");
      }

      if (state.bolus) {
        annotate("Programmed bolus", state.bolus.programmed);
        annotate("Delivered bolus", state.bolus.amount);
        annotate("Percent delivered: ", (state.bolus.amount/state.bolus.programmed * 100).toString( ) + '%');
      }
      if (state.square) {
        annotate("Programmed square", state.square.programmed);
        annotate("Delivered square", state.square.amount);
        annotate("Success: ", (state.square.amount/state.square.programmed * 100).toString( ) + '%');
      }
      if (state.wizard) {
        state.created_at = state.wizard.timestamp;
        annotate("Food estimate", state.wizard.food_estimate);
        annotate("Correction estimate", state.wizard.correction_estimate);
        annotate("Bolus estimate", state.wizard.bolus_estimate);
        annotate("Target low", state.wizard.bg_target_low);
        annotate("Target high", state.wizard.bg_target_high);
        var delta = state.wizard.sensitivity * state.insulin * -1;
        annotate("Hypothetical glucose delta", delta);
        if (state.bg && state.bg > 0) {
          annotate('Glucose was:', state.bg);
          // state.glucose = state.bg;
          // TODO: annotate prediction
        }
      }
      if (state.carbs && state.insulin && state.bg) {
        state.eventType = 'Meal Bolus';
      } else {
        if (has_carbs && !has_insulin) {
          state.eventType = 'Carb Correction';
        }
        if (!has_carbs && has_insulin) {
          state.eventType = 'Correction Bolus';
        }
      }
      if (state.notes && state.notes.length > 0) {
        state.notes = state.notes.join("\n");
      }
      if (state.insulin) {
        state.insulin = state.insulin.toString( );
      }

      results.push(state);
      state = { };
    }
  }

  function annotate (msg) {
    var args = [ ].slice.apply(arguments);
    msg = args.join(' ');
    if (!state.notes) {
      state.notes = [ ];
    }
    state.notes.push(msg);
  }

  function step (current, index) {
    if (in_previous(current)) {
      return;
    }
    switch (current._type) {
      case 'Bolus':
      case 'BolusWizard':
        var tail = within_minutes_from(current, treatments.slice(index+1), 4);
        bolus(current, tail);
        break;
      default:
        results.push(current);
        break;
    }
  }
  treatments.forEach(step);
  return results;
}

exports = module.exports = reduce;
<commit_msg>fix_within_minutes_from<commit_after>
function reduce (treatments) {

  var results = [ ];

  var state = { };
  var previous = [ ];

  function in_previous (ev) {
    var found = false;
    previous.forEach(function (elem) {
      if (elem.timestamp == ev.timestamp && ev._type == elem._type) {
        found = true;
      }
    });

    return found;
  }

  function within_minutes_from (origin, tail, minutes) {
    var ms = minutes * 1000 * 60;
    var ts = Date.parse(origin.timestamp);
    var candidates = tail.slice( ).filter(function (elem) {
      var dt = Date.parse(elem.timestamp);
      return ts - dt <= ms;

    });
    return candidates;
  }

  function bolus (ev, remaining) {
    if (!ev) { console.error('XXX', ev, remaining); return; }
    if (ev._type == 'BolusWizard') {
      state.carbs = ev.carb_input.toString( );
      state.ratio = ev.carb_ratio.toString( );
      state.bg = ev.bg.toString( );
      state.glucose = ev.bg.toString( );
      state.glucoseType = ev._type;
      state.wizard = ev;
      state.created_at = state.timestamp = ev.timestamp;
      previous.push(ev);
    }

    if (ev._type == 'Bolus') {
      state.duration = ev.duration.toString( );
      // if (state.square || state.bolus) { }
      // state.insulin = (state.insulin ? state.insulin : 0) + ev.amount;
      if (ev.duration && ev.duration > 0) {
        state.square = ev;
      } else {
        state.bolus = ev;
      }
      state.created_at = state.timestamp = ev.timestamp;
      previous.push(ev);
    }

    if (remaining && remaining.length > 0) {
      if (state.bolus && state.wizard) {
        // skip to end
        return bolus({}, []);
      }
      // keep recursing
      return bolus(remaining[0], remaining.slice(1));
    } else {
      // console.error("state", state);
      // console.error("remaining", remaining);
      state.eventType = '<none>';

      state.insulin = (state.square ? state.square.amount : 0) +
                      (state.bolus ? state.bolus.amount : 0);
      var has_insulin = state.insulin && state.insulin > 0;
      var has_carbs = state.carbs && state.carbs > 0;
      var has_wizard = state.wizard ? true : false;
      var has_bolus = state.bolus ? true : false;
      if (state.square && state.bolus) {
        annotate("DualWave bolus for", state.square.duration, "minutes");
      } else if (state.square && state.wizard) {
        annotate("Square wave bolus for", state.square.duration, "minutes");
      } else if (state.square) {
        annotate("Solo Square wave bolus for", state.square.duration, "minutes");
        annotate("No bolus wizard used.");
      } else if (state.bolus && state.wizard) {
        annotate("Normal bolus with wizard.");
      } else if (state.bolus) {
        annotate("Normal bolus (solo, no bolus wizard).");
      }

      if (state.bolus) {
        annotate("Programmed bolus", state.bolus.programmed);
        annotate("Delivered bolus", state.bolus.amount);
        annotate("Percent delivered: ", (state.bolus.amount/state.bolus.programmed * 100).toString( ) + '%');
      }
      if (state.square) {
        annotate("Programmed square", state.square.programmed);
        annotate("Delivered square", state.square.amount);
        annotate("Success: ", (state.square.amount/state.square.programmed * 100).toString( ) + '%');
      }
      if (state.wizard) {
        state.created_at = state.wizard.timestamp;
        annotate("Food estimate", state.wizard.food_estimate);
        annotate("Correction estimate", state.wizard.correction_estimate);
        annotate("Bolus estimate", state.wizard.bolus_estimate);
        annotate("Target low", state.wizard.bg_target_low);
        annotate("Target high", state.wizard.bg_target_high);
        var delta = state.wizard.sensitivity * state.insulin * -1;
        annotate("Hypothetical glucose delta", delta);
        if (state.bg && state.bg > 0) {
          annotate('Glucose was:', state.bg);
          // state.glucose = state.bg;
          // TODO: annotate prediction
        }
      }
      if (state.carbs && state.insulin && state.bg) {
        state.eventType = 'Meal Bolus';
      } else {
        if (has_carbs && !has_insulin) {
          state.eventType = 'Carb Correction';
        }
        if (!has_carbs && has_insulin) {
          state.eventType = 'Correction Bolus';
        }
      }
      if (state.notes && state.notes.length > 0) {
        state.notes = state.notes.join("\n");
      }
      if (state.insulin) {
        state.insulin = state.insulin.toString( );
      }

      results.push(state);
      state = { };
    }
  }

  function annotate (msg) {
    var args = [ ].slice.apply(arguments);
    msg = args.join(' ');
    if (!state.notes) {
      state.notes = [ ];
    }
    state.notes.push(msg);
  }

  function step (current, index) {
    if (in_previous(current)) {
      return;
    }
    switch (current._type) {
      case 'Bolus':
      case 'BolusWizard':
        var tail = within_minutes_from(current, treatments.slice(index+1), 4);
        bolus(current, tail);
        break;
      default:
        results.push(current);
        break;
    }
  }
  treatments.forEach(step);
  return results;
}

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