<commit_before>/**
 * @license
 * Copyright 2018 Google Inc. All Rights Reserved.
 * 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/licnses/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.
 * =============================================================================
 */
import * as tf from '@tensorflow/tfjs-core';
import dat from 'dat.gui';
import Stats from 'stats.js';
import posenet, {checkpoints} from '../src';

import {drawKeypoints, drawSkeleton} from './demo_util';
const maxStride = 32;
const videoSizes = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16].map(
  (multiplier) => (maxStride * multiplier + 1));
const maxVideoSize = 513;
const canvasSize = 400;
const stats = new Stats();

async function getCameras() {
  const devices = await navigator.mediaDevices.enumerateDevices();
  return devices.filter(({kind}) => kind === 'videoinput');
}

let currentStream = null;

function stopCurrentVideoStream() {
  if (currentStream) {
    currentStream.getTracks().forEach((track) => {
      track.stop();
    });
  }
}

function loadVideo(cameraId) {
  return new Promise((resolve, reject) => {
    stopCurrentVideoStream();

    const video = document.getElementById('video');

    video.width = maxVideoSize;
    video.height = maxVideoSize;

    if (navigator.getUserMedia) {
      navigator.getUserMedia({
        video: {
          width: maxVideoSize,
          height: maxVideoSize,
          deviceId: {exact: cameraId},
        },
      }, handleVideo, videoError);
    }

    function handleVideo(stream) {
      currentStream = stream;
      video.srcObject = stream;

      resolve(video);
    }

    function videoError(e) {
      // do something
      reject(e);
    }
  });
}

const guiState = {
  algorithm: 'single-pose',
  input: {
    mobileNetArchitecture: '101',
    outputStride: 16,
    inputImageResolution: 225,
  },
  singlePoseDetection: {
    minPoseConfidence: 0.1,
    minPartConfidence: 0.5,
  },
  multiPoseDetection: {
    maxPoseDetections: 2,
    minPoseConfidence: 0.1,
    minPartConfidence: 0.3,
    nmsRadius: 20.0,
  },
  output: {
    showVideo: true,
    showSkeleton: true,
    showPoints: true,
  },
  net: null,
};

function setupGui(cameras, net) {
  guiState.net = net;

  if (cameras.length > 0) {
    guiState.camera = cameras[0].deviceId;
  }

  const cameraOptions = cameras.reduce((result, {label, deviceId}) => {
    result[label] = deviceId;
    return result;
  }, {});

  const gui = new dat.GUI({width: 300});

  gui.add(guiState, 'camera', cameraOptions).onChange((deviceId) => {
    loadVideo(deviceId);
  });
  const algorithmController = gui.add(
    guiState, 'algorithm', ['single-pose', 'multi-pose'] );

  let input = gui.addFolder('Input');
  const architectureController =
    input.add(guiState.input, 'mobileNetArchitecture', Object.keys(checkpoints));
  input.add(guiState.input, 'outputStride', [8, 16, 32]);
  input.add(guiState.input, 'inputImageResolution', videoSizes);
  input.open();

  let single = gui.addFolder('Single Pose Detection');
  single.add(guiState.singlePoseDetection, 'minPoseConfidence', 0.0, 1.0);
  single.add(guiState.singlePoseDetection, 'minPartConfidence', 0.0, 1.0);
  single.open();

  let multi = gui.addFolder('Multi Pose Detection');
  multi.add(
    guiState.multiPoseDetection, 'maxPoseDetections').min(1).max(20).step(1);
  multi.add(guiState.multiPoseDetection, 'minPoseConfidence', 0.0, 1.0);
  multi.add(guiState.multiPoseDetection, 'minPartConfidence', 0.0, 1.0);
  multi.add(guiState.multiPoseDetection, 'nmsRadius').min(0.0).max(40.0);

  let output = gui.addFolder('Output');
  output.add(guiState.output, 'showVideo');
  output.add(guiState.output, 'showSkeleton');
  output.add(guiState.output, 'showPoints');
  output.open();

  architectureController.onChange(function(architecture) {
    guiState.changeToArchitecture = architecture;
  });

  algorithmController.onChange(function(value) {
    switch (guiState.algorithm) {
    case 'single-pose':
      multi.close();
      single.open();
      break;
    case 'multi-pose':
      single.close();
      multi.open();
      break;
    }
  });
}

function setupFPS() {
  stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom
  document.body.appendChild(stats.dom);
}

function detectPoseInRealTime(video, net) {
  const canvas = document.getElementById('output');
  const ctx = canvas.getContext('2d');

  canvas.width = canvasSize;
  canvas.height = canvasSize;

  async function poseDetectionFrame() {
    if (guiState.changeToArchitecture) {
      const checkpoint = checkpoints[guiState.changeToArchitecture];

      guiState.net.dispose();

      guiState.net = await posenet(checkpoint.url, checkpoint.architecture);

      guiState.changeToArchitecture = null;
    }

    stats.begin();

    const inputImageResolution = Number(guiState.input.inputImageResolution);
    const outputStride = Number(guiState.input.outputStride);

    const originalImage = tf.fromPixels(video);
    const image = originalImage.reverse(1).resizeBilinear(
      [inputImageResolution, inputImageResolution]);

    const scale = canvasSize / inputImageResolution;

    let poses = [];
    let minPoseConfidence;
    let minPartConfidence;
    switch (guiState.algorithm) {
    case 'single-pose':
      const pose = await guiState.net.estimateSinglePose(image, outputStride);
      poses.push(pose);

      minPoseConfidence = Number(
        guiState.singlePoseDetection.minPoseConfidence);
      minPartConfidence = Number(
        guiState.singlePoseDetection.minPartConfidence);
      break;
    case 'multi-pose':
      poses = await guiState.net.estimateMultiplePoses(image, outputStride,
        guiState.multiPoseDetection.maxPoseDetections,
        guiState.multiPoseDetection.minPartConfidence,
        guiState.multiPoseDetection.nmsRadius);

      minPoseConfidence = Number(guiState.multiPoseDetection.minPoseConfidence);
      minPartConfidence = Number(guiState.multiPoseDetection.minPartConfidence);
      break;
    }

    ctx.clearRect(0, 0, canvasSize, canvasSize);

    if (guiState.output.showVideo) {
      ctx.save();
      ctx.scale(-1, 1);
      ctx.drawImage(video, 0, 0, canvasSize*-1, canvasSize);
      ctx.restore();
    }

    poses.forEach(({score, keypoints}) => {
      if (score >= minPoseConfidence) {
        if (guiState.output.showPoints) {
          drawKeypoints(keypoints, minPartConfidence, ctx, scale);
        }
        if (guiState.output.showSkeleton) {
          drawSkeleton(keypoints, minPartConfidence, ctx, scale);
        }
      }
    });

    image.dispose();
    originalImage.dispose();

    stats.end();

    requestAnimationFrame(poseDetectionFrame);
  }

  poseDetectionFrame();
}

export async function bindPage() {
  const net = await posenet();

  document.getElementById('loading').style.display = 'none';
  document.getElementById('main').style.display = 'block';

  const cameras = await getCameras();

  if (cameras.length === 0) {
    alert('No webcams available.  Reload the page when a webcam is available.');
    return;
  }

  const video = await loadVideo(cameras[0].deviceId);

  setupGui(cameras, net);
  setupFPS();
  detectPoseInRealTime(video, net);
}

navigator.getUserMedia = navigator.getUserMedia ||
  navigator.webkitGetUserMedia ||
  navigator.mozGetUserMedia;
bindPage();
<commit_msg>fixed default camera model<commit_after>/**
 * @license
 * Copyright 2018 Google Inc. All Rights Reserved.
 * 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/licnses/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.
 * =============================================================================
 */
import * as tf from '@tensorflow/tfjs-core';
import dat from 'dat.gui';
import Stats from 'stats.js';
import posenet, {checkpoints} from '../src';

import {drawKeypoints, drawSkeleton} from './demo_util';
const maxStride = 32;
const videoSizes = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16].map(
  (multiplier) => (maxStride * multiplier + 1));
const maxVideoSize = 513;
const canvasSize = 400;
const stats = new Stats();

async function getCameras() {
  const devices = await navigator.mediaDevices.enumerateDevices();
  return devices.filter(({kind}) => kind === 'videoinput');
}

let currentStream = null;

function stopCurrentVideoStream() {
  if (currentStream) {
    currentStream.getTracks().forEach((track) => {
      track.stop();
    });
  }
}

function loadVideo(cameraId) {
  return new Promise((resolve, reject) => {
    stopCurrentVideoStream();

    const video = document.getElementById('video');

    video.width = maxVideoSize;
    video.height = maxVideoSize;

    if (navigator.getUserMedia) {
      navigator.getUserMedia({
        video: {
          width: maxVideoSize,
          height: maxVideoSize,
          deviceId: {exact: cameraId},
        },
      }, handleVideo, videoError);
    }

    function handleVideo(stream) {
      currentStream = stream;
      video.srcObject = stream;

      resolve(video);
    }

    function videoError(e) {
      // do something
      reject(e);
    }
  });
}

const guiState = {
  algorithm: 'single-pose',
  input: {
    mobileNetArchitecture: '1.01',
    outputStride: 16,
    inputImageResolution: 225,
  },
  singlePoseDetection: {
    minPoseConfidence: 0.1,
    minPartConfidence: 0.5,
  },
  multiPoseDetection: {
    maxPoseDetections: 2,
    minPoseConfidence: 0.1,
    minPartConfidence: 0.3,
    nmsRadius: 20.0,
  },
  output: {
    showVideo: true,
    showSkeleton: true,
    showPoints: true,
  },
  net: null,
};

function setupGui(cameras, net) {
  guiState.net = net;

  if (cameras.length > 0) {
    guiState.camera = cameras[0].deviceId;
  }

  const cameraOptions = cameras.reduce((result, {label, deviceId}) => {
    result[label] = deviceId;
    return result;
  }, {});

  const gui = new dat.GUI({width: 300});

  gui.add(guiState, 'camera', cameraOptions).onChange((deviceId) => {
    loadVideo(deviceId);
  });
  const algorithmController = gui.add(
    guiState, 'algorithm', ['single-pose', 'multi-pose'] );

  let input = gui.addFolder('Input');
  const architectureController =
    input.add(guiState.input, 'mobileNetArchitecture', Object.keys(checkpoints));
  input.add(guiState.input, 'outputStride', [8, 16, 32]);
  input.add(guiState.input, 'inputImageResolution', videoSizes);
  input.open();

  let single = gui.addFolder('Single Pose Detection');
  single.add(guiState.singlePoseDetection, 'minPoseConfidence', 0.0, 1.0);
  single.add(guiState.singlePoseDetection, 'minPartConfidence', 0.0, 1.0);
  single.open();

  let multi = gui.addFolder('Multi Pose Detection');
  multi.add(
    guiState.multiPoseDetection, 'maxPoseDetections').min(1).max(20).step(1);
  multi.add(guiState.multiPoseDetection, 'minPoseConfidence', 0.0, 1.0);
  multi.add(guiState.multiPoseDetection, 'minPartConfidence', 0.0, 1.0);
  multi.add(guiState.multiPoseDetection, 'nmsRadius').min(0.0).max(40.0);

  let output = gui.addFolder('Output');
  output.add(guiState.output, 'showVideo');
  output.add(guiState.output, 'showSkeleton');
  output.add(guiState.output, 'showPoints');
  output.open();

  architectureController.onChange(function(architecture) {
    guiState.changeToArchitecture = architecture;
  });

  algorithmController.onChange(function(value) {
    switch (guiState.algorithm) {
    case 'single-pose':
      multi.close();
      single.open();
      break;
    case 'multi-pose':
      single.close();
      multi.open();
      break;
    }
  });
}

function setupFPS() {
  stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom
  document.body.appendChild(stats.dom);
}

function detectPoseInRealTime(video, net) {
  const canvas = document.getElementById('output');
  const ctx = canvas.getContext('2d');

  canvas.width = canvasSize;
  canvas.height = canvasSize;

  async function poseDetectionFrame() {
    if (guiState.changeToArchitecture) {
      guiState.net.dispose();

      guiState.net = await posenet(guiState.changeToArchitecture);

      guiState.changeToArchitecture = null;
    }

    stats.begin();

    const inputImageResolution = Number(guiState.input.inputImageResolution);
    const outputStride = Number(guiState.input.outputStride);

    const originalImage = tf.fromPixels(video);
    const image = originalImage.reverse(1).resizeBilinear(
      [inputImageResolution, inputImageResolution]);

    const scale = canvasSize / inputImageResolution;

    let poses = [];
    let minPoseConfidence;
    let minPartConfidence;
    switch (guiState.algorithm) {
    case 'single-pose':
      const pose = await guiState.net.estimateSinglePose(image, outputStride);
      poses.push(pose);

      minPoseConfidence = Number(
        guiState.singlePoseDetection.minPoseConfidence);
      minPartConfidence = Number(
        guiState.singlePoseDetection.minPartConfidence);
      break;
    case 'multi-pose':
      poses = await guiState.net.estimateMultiplePoses(image, outputStride,
        guiState.multiPoseDetection.maxPoseDetections,
        guiState.multiPoseDetection.minPartConfidence,
        guiState.multiPoseDetection.nmsRadius);

      minPoseConfidence = Number(guiState.multiPoseDetection.minPoseConfidence);
      minPartConfidence = Number(guiState.multiPoseDetection.minPartConfidence);
      break;
    }

    ctx.clearRect(0, 0, canvasSize, canvasSize);

    if (guiState.output.showVideo) {
      ctx.save();
      ctx.scale(-1, 1);
      ctx.drawImage(video, 0, 0, canvasSize*-1, canvasSize);
      ctx.restore();
    }

    poses.forEach(({score, keypoints}) => {
      if (score >= minPoseConfidence) {
        if (guiState.output.showPoints) {
          drawKeypoints(keypoints, minPartConfidence, ctx, scale);
        }
        if (guiState.output.showSkeleton) {
          drawSkeleton(keypoints, minPartConfidence, ctx, scale);
        }
      }
    });

    image.dispose();
    originalImage.dispose();

    stats.end();

    requestAnimationFrame(poseDetectionFrame);
  }

  poseDetectionFrame();
}

export async function bindPage() {
  const net = await posenet();

  document.getElementById('loading').style.display = 'none';
  document.getElementById('main').style.display = 'block';

  const cameras = await getCameras();

  if (cameras.length === 0) {
    alert('No webcams available.  Reload the page when a webcam is available.');
    return;
  }

  const video = await loadVideo(cameras[0].deviceId);

  setupGui(cameras, net);
  setupFPS();
  detectPoseInRealTime(video, net);
}

navigator.getUserMedia = navigator.getUserMedia ||
  navigator.webkitGetUserMedia ||
  navigator.mozGetUserMedia;
bindPage();
<|endoftext|>


<commit_before>/**
 * Adaptation of the HTML5 structured cloning alogrithm.
 *
 * Can recursively clone objects, including those containing number, boolean,
 * string, date, and regex objects. It can also clone objects which included
 * cyclic references to itself, including nested cyclic references.
 *
 * Works in ES5-compatible browsers.
 *
 * @todo HTMLEntities
 * @todo Tests and Dox
 * @todo Does this really work?
 * @todo structural typing for custom cloning procedures?
 * @todo optsHash?
 */

(function(root) {
  'use strict';

  var __call__ = Function.prototype.call;
  var _hasProp = __call__.bind({}.hasOwnProperty);
  var _toString = __call__.bind({}.toString);

  /**
   * Utilities for working with transfer maps. A transfer map is defined as an
   * object that has two properties, `inputs` and `outputs`, each of which
   * are arrays where for any object at inputs[i], the output value that should
   * be mapped to the cloned object for that object resides at outputs[i]. See
   * the W3 spec for more details. This was the closest I could get without
   * having to set custom properties on objects, which wouldn't work for
   * immutable objects anyway.
   */
  function TransferMap() {
    this.inputs = [];
    this.outputs = [];
  }

  TransferMap.prototype.set = function(input, output) {
    if (this.inputs.indexOf(input) === -1) {
      this.inputs.push(input);
      this.outputs.push(output);
    }
  };

  TransferMap.prototype.get = function(input) {
    var idx = this.inputs.indexOf(input);
    var output = null;

    if (idx > -1) {
      output = this.outputs[idx];
    }

    return output;
  };

  function _iSClone(input, tMap) {
    if (input === null) {
      return null;
    }

    if (typeof input === 'object') {
      return _handleObjectClone(input, tMap);
    }

    return input;
  }

  function _handleObjectClone(input, tMap) {
    var _selfRef = tMap.get(input);
    if (_selfRef !== null) {
      return _selfRef;
    }

    var val = input.valueOf();
    var obType = _toString(input);
    var output;
    var isCollection = false;

    switch (obType) {
      case '[object String]':
        output = new String(val);
        break;

      case '[object Number]':
        output = new Number(val);
        break;

      case '[object Boolean]':
        output = new Boolean(val);
        break;

      case '[object Date]':
        output = new Date(val);
        break;

      case '[object RegExp]':
        output = new RegExp(val);
        break;

      case '[object Array]':
        output = new Array(input.length);
        isCollection = true;
        break;

      case '[object Object]':
        if (typeof input.constructor === 'function') {
          output = new input.constructor();
        } else { // Handle Object.create(null)
          output = {};
        }

        isCollection = true;
        break;

      default:
        throw new TypeError(
          "Don't know how to clone object of type " + obType
        );
    }

    tMap.set(input, output);

    if (isCollection) {
      _handleCollectionClone(input, output, tMap);
    }

    return output;
  }

  function _handleCollectionClone(input, output, tMap) {
    var prop;

    for (prop in input) {
      if (_hasProp(input, prop)) {
        output[prop] = _iSClone(input[prop], tMap);
      }
    }
  }

  var CY = {
    clone: function(input) {
      return _iSClone(input, new TransferMap());
    }
  };

  if (typeof module === 'object' && typeof module.exports === 'object') {
    // Node
    module.exports = CY;
  } else {
    // Not Node
    root.CY = CY;
  }
})(this);
<commit_msg>Use Object.create() to instantiate copies of objects instead of explicitly calling constructors. Use Docco syntax<commit_after>// Cyclone.js: An Adaptation of the HTML5 structured cloning alogrithm.
//
// Can recursively clone objects, including those containing number, boolean,
// string, date, and regex objects. It can also clone objects which included
// cyclic references to itself, including nested cyclic references.
//
// Works in ES5-compatible browsers.
//
// @todo Tests and Dox
// @todo structural typing for custom cloning procedures?
// @todo optsHash?

(function(root) {
  'use strict';

  var __call__ = Function.prototype.call;
  var _hasProp = __call__.bind({}.hasOwnProperty);
  var _toString = __call__.bind({}.toString);

  // Utilities for working with transfer maps. A transfer map is defined as an
  // object that has two properties, `inputs` and `outputs`, each of which
  // are arrays where for any object at inputs[i], the output value that should
  // be mapped to the cloned object for that object resides at outputs[i]. See
  // the W3 spec for more details. This was the closest I could get without
  // having to set custom properties on objects, which wouldn't work for
  // immutable objects anyway.
  function TransferMap() {
    this.inputs = [];
    this.outputs = [];
  }

  TransferMap.prototype.set = function(input, output) {
    if (this.inputs.indexOf(input) === -1) {
      this.inputs.push(input);
      this.outputs.push(output);
    }
  };

  TransferMap.prototype.get = function(input) {
    var idx = this.inputs.indexOf(input);
    var output = null;

    if (idx > -1) {
      output = this.outputs[idx];
    }

    return output;
  };

  function _iSClone(input, tMap) {
    if (input === null) {
      return null;
    }

    if (typeof input === 'object') {
      return _handleObjectClone(input, tMap);
    }

    return input;
  }

  function _handleObjectClone(input, tMap) {
    var _selfRef = tMap.get(input);
    if (_selfRef !== null) {
      return _selfRef;
    }

    var val = input.valueOf();
    var obType = _toString(input);
    var output;
    var isCollection = false;

    switch (obType) {
      case '[object String]':
        output = new String(val);
        break;

      case '[object Number]':
        output = new Number(val);
        break;

      case '[object Boolean]':
        output = new Boolean(val);
        break;

      case '[object Date]':
        output = new Date(val);
        break;

      case '[object RegExp]':
        output = new RegExp(val);
        break;

      case '[object Array]':
        output = new Array(input.length);
        isCollection = true;
        break;

      case '[object Object]':
        output = Object.create(Object.getPrototypeOf(input));
        isCollection = true;
        break;

      default:
        throw new TypeError(
          "Don't know how to clone object of type " + obType
        );
    }

    tMap.set(input, output);

    if (isCollection) {
      _handleCollectionClone(input, output, tMap);
    }

    return output;
  }

  function _handleCollectionClone(input, output, tMap) {
    var prop;

    for (prop in input) {
      if (_hasProp(input, prop)) {
        output[prop] = _iSClone(input[prop], tMap);
      }
    }
  }

  var CY = {
    clone: function(input) {
      return _iSClone(input, new TransferMap());
    }
  };

  if (typeof module === 'object' && typeof module.exports === 'object') {
    // Node
    module.exports = CY;
  } else {
    // Not Node
    root.CY = CY;
  }
})(this);
<|endoftext|>


<commit_before>var constants = require('./constants.js');
var query = require('./query.js');
var util = require('./util.js');

var getPseudoContent = function(node, selector) {
	var styles = window.getComputedStyle(node, selector);
	var ret = styles.getPropertyValue('content');
	var inline = styles.display.substr(0, 6) === 'inline';
	if (!ret) {
		return '';
	}
	if (ret.substr(0, 1) !== '"') {
		return '';
	} else {
		if (inline) {
			return ret.slice(1, -1);
		} else {
			return ' ' + ret.slice(1, -1) + ' ';
		}
	}
};

var getContent = function(root, referenced, owned) {
	var children = [];

	for (var i = 0; i < root.childNodes.length; i++) {
		var node = root.childNodes[i];
		if (!node.id || !document.querySelector('[aria-owns~="' + node.id + '"]')) {
			children.push(node);
		}
	}

	var owns = query.getAttribute(root, 'owns') || [];
	for (var i = 0; i < owns.length; i++) {
		var child = document.getElementById(owns[i]);
		if (child && child !== root && owned.indexOf(child.id) === -1) {
			children.push(child);
			owned.push(child.id);
		}
	}

	var ret = '';
	for (var i = 0; i < children.length; i++) {
		var node = children[i];
		if (node.nodeType === node.TEXT_NODE) {
			ret += node.textContent;
		} else if (node.nodeType === node.ELEMENT_NODE) {
			if (node.tagName.toLowerCase() === 'br') {
				ret += '\n';
			} else if (window.getComputedStyle(node).display.substr(0, 6) === 'inline' &&
					node.tagName.toLowerCase() !== 'input' &&
					node.tagName.toLowerCase() !== 'img') {  // https://github.com/w3c/accname/issues/3
				ret += getName(node, true, referenced, owned);
			} else {
				ret += ' ' + getName(node, true, referenced, owned) + ' ';
			}
		}
	}

	return ret;
};

var allowNameFromContent = function(el) {
	var role = query.getRole(el);
	return !role || constants.nameFromContents.indexOf(role) !== -1;
};

var isLabelable = function(el) {
	var selector = constants.labelable.join(',');
	return el.matches(selector);
};

// Control.labels is part of the standard, but not supported in most browsers
var getLabelNodes = function(element) {
	var labels = [];
	var labelable = constants.labelable.join(',');
	util.walkDOM(document.body, function(node) {
		if (node.tagName && node.tagName.toLowerCase() === 'label') {
			if (node.getAttribute('for')) {
				if (element.id && node.getAttribute('for') === element.id) {
					labels.push(node);
				}
			} else if (node.querySelector(labelable) === element) {
				labels.push(node);
			}
		}
	});
	return labels;
};

var isInLabelForOtherWidget = function(el) {
	var label = el.closest('label');
	var ownLabels = getLabelNodes(el);
	return label && ownLabels.indexOf(label) === -1;
};

// http://www.ssbbartgroup.com/blog/how-the-w3c-text-alternative-computation-works/
// https://www.w3.org/TR/accname-aam-1.1/#h-mapping_additional_nd_te
var getName = function(el, recursive, referenced, owned) {
	var ret = '';
	var owned = owned || [];

	// A
	if (query.getAttribute(el, 'hidden', referenced)) {
		return '';
	}

	// B
	if (!recursive && el.matches('[aria-labelledby]')) {
		var ids = el.getAttribute('aria-labelledby').split(/\s+/);
		var strings = ids.map(function(id) {
			var label = document.getElementById(id);
			return label ? getName(label, true, label, owned) : '';
		});
		ret = strings.join(' ');
	}

	// C
	if (!ret.trim() && el.matches('[aria-label]')) {
		ret = el.getAttribute('aria-label');
	}

	// FIXME: not mentioned in the spec
	if (!ret.trim() && query.matches(el, 'presentation')) {
		return getContent(el, referenced, owned);
	}

	// D
	if (!ret.trim() && !recursive && isLabelable(el)) {
		var strings = getLabelNodes(el).map(function(label) {
			return getName(label, true, label, owned);
		});
		ret = strings.join(' ');
	}
	if (!ret.trim()) {
		ret = el.placeholder || '';
	}
	if (!ret.trim()) {
		ret = el.alt || '';
	}
	if (!ret.trim() && el.matches('abbr,acronym') && el.title) {
		ret = el.title;
	}
	if (!ret.trim()) {
		for (var selector in constants.nameFromDescendant) {
			if (el.matches(selector)) {
				var descendant = el.querySelector(constants.nameFromDescendant[selector]);
				if (descendant) {
					ret = getName(descendant, true, descendant, owned);
				}
			}
		}
	}

	// E
	if (!ret.trim() && (recursive || isInLabelForOtherWidget(el) || query.matches(el, 'button'))) {
		if (query.matches(el, 'textbox,button,combobox,listbox,range')) {
			if (query.matches(el, 'textbox,button')) {
				ret = el.value || el.textContent;
			} else if (query.matches(el, 'combobox,listbox')) {
				var selected = query.querySelector(el, ':selected') || query.querySelector(el, 'option');
				if (selected) {
					ret = getName(selected, recursive, referenced, owned);
				}
			} else if (query.matches(el, 'range')) {
				ret = '' + (query.getAttribute(el, 'valuetext') || query.getAttribute(el, 'valuenow') || el.value);
			}
		}
	}

	// F
	if (!ret.trim() && (recursive || allowNameFromContent(el)) && !query.matches(el, 'menu')) {
		ret = getContent(el, referenced, owned);
	}

	// TODO: G
	// TODO: H

	// FIXME: not mentioned in the spec
	if (!ret.trim()) {
		for (var selector in constants.nameDefaults) {
			if (el.matches(selector)) {
				ret = constants.nameDefaults[selector];
			}
		}
	}

	// I
	if (!ret.trim()) {
		ret = el.title || '';
	}

	var before = getPseudoContent(el, ':before');
	var after = getPseudoContent(el, ':after');
	return before + ret + after;
};

var getNameTrimmed = function(el) {
	return getName(el).replace(/\s+/g, ' ').trim();
};

var getDescription = function(el) {
	var ret = '';
	var owned = [];

	if (el.matches('[aria-describedby]')) {
		var ids = el.getAttribute('aria-describedby').split(/\s+/);
		var strings = ids.map(function(id) {
			var label = document.getElementById(id);
			return label ? getName(label, true, label, owned) : '';
		});
		ret = strings.join(' ');
	} else if (el.title) {
		ret = el.title;
	} else if (el.placeholder) {
		ret = el.placeholder;
	}

	ret = (ret || '').trim().replace(/\s+/g, ' ');

	if (ret === getNameTrimmed(el)) {
		ret = '';
	}

	return ret;
};

module.exports = {
	getName: getNameTrimmed,
	getDescription: getDescription,
};
<commit_msg>improve condiion for F<commit_after>var constants = require('./constants.js');
var query = require('./query.js');
var util = require('./util.js');

var getPseudoContent = function(node, selector) {
	var styles = window.getComputedStyle(node, selector);
	var ret = styles.getPropertyValue('content');
	var inline = styles.display.substr(0, 6) === 'inline';
	if (!ret) {
		return '';
	}
	if (ret.substr(0, 1) !== '"') {
		return '';
	} else {
		if (inline) {
			return ret.slice(1, -1);
		} else {
			return ' ' + ret.slice(1, -1) + ' ';
		}
	}
};

var getContent = function(root, referenced, owned) {
	var children = [];

	for (var i = 0; i < root.childNodes.length; i++) {
		var node = root.childNodes[i];
		if (!node.id || !document.querySelector('[aria-owns~="' + node.id + '"]')) {
			children.push(node);
		}
	}

	var owns = query.getAttribute(root, 'owns') || [];
	for (var i = 0; i < owns.length; i++) {
		var child = document.getElementById(owns[i]);
		if (child && child !== root && owned.indexOf(child.id) === -1) {
			children.push(child);
			owned.push(child.id);
		}
	}

	var ret = '';
	for (var i = 0; i < children.length; i++) {
		var node = children[i];
		if (node.nodeType === node.TEXT_NODE) {
			ret += node.textContent;
		} else if (node.nodeType === node.ELEMENT_NODE) {
			if (node.tagName.toLowerCase() === 'br') {
				ret += '\n';
			} else if (window.getComputedStyle(node).display.substr(0, 6) === 'inline' &&
					node.tagName.toLowerCase() !== 'input' &&
					node.tagName.toLowerCase() !== 'img') {  // https://github.com/w3c/accname/issues/3
				ret += getName(node, true, referenced, owned);
			} else {
				ret += ' ' + getName(node, true, referenced, owned) + ' ';
			}
		}
	}

	return ret;
};

var allowNameFromContent = function(el) {
	var role = query.getRole(el);
	return role && constants.nameFromContents.indexOf(role) !== -1;
};

var isLabelable = function(el) {
	var selector = constants.labelable.join(',');
	return el.matches(selector);
};

// Control.labels is part of the standard, but not supported in most browsers
var getLabelNodes = function(element) {
	var labels = [];
	var labelable = constants.labelable.join(',');
	util.walkDOM(document.body, function(node) {
		if (node.tagName && node.tagName.toLowerCase() === 'label') {
			if (node.getAttribute('for')) {
				if (element.id && node.getAttribute('for') === element.id) {
					labels.push(node);
				}
			} else if (node.querySelector(labelable) === element) {
				labels.push(node);
			}
		}
	});
	return labels;
};

var isInLabelForOtherWidget = function(el) {
	var label = el.closest('label');
	var ownLabels = getLabelNodes(el);
	return label && ownLabels.indexOf(label) === -1;
};

// http://www.ssbbartgroup.com/blog/how-the-w3c-text-alternative-computation-works/
// https://www.w3.org/TR/accname-aam-1.1/#h-mapping_additional_nd_te
var getName = function(el, recursive, referenced, owned) {
	var ret = '';
	var owned = owned || [];

	// A
	if (query.getAttribute(el, 'hidden', referenced)) {
		return '';
	}

	// B
	if (!recursive && el.matches('[aria-labelledby]')) {
		var ids = el.getAttribute('aria-labelledby').split(/\s+/);
		var strings = ids.map(function(id) {
			var label = document.getElementById(id);
			return label ? getName(label, true, label, owned) : '';
		});
		ret = strings.join(' ');
	}

	// C
	if (!ret.trim() && el.matches('[aria-label]')) {
		ret = el.getAttribute('aria-label');
	}

	// FIXME: not mentioned in the spec
	if (!ret.trim() && query.matches(el, 'presentation')) {
		return getContent(el, referenced, owned);
	}

	// D
	if (!ret.trim() && !recursive && isLabelable(el)) {
		var strings = getLabelNodes(el).map(function(label) {
			return getName(label, true, label, owned);
		});
		ret = strings.join(' ');
	}
	if (!ret.trim()) {
		ret = el.placeholder || '';
	}
	if (!ret.trim()) {
		ret = el.alt || '';
	}
	if (!ret.trim() && el.matches('abbr,acronym') && el.title) {
		ret = el.title;
	}
	if (!ret.trim()) {
		for (var selector in constants.nameFromDescendant) {
			if (el.matches(selector)) {
				var descendant = el.querySelector(constants.nameFromDescendant[selector]);
				if (descendant) {
					ret = getName(descendant, true, descendant, owned);
				}
			}
		}
	}

	// E
	if (!ret.trim() && (recursive || isInLabelForOtherWidget(el) || query.matches(el, 'button'))) {
		if (query.matches(el, 'textbox,button,combobox,listbox,range')) {
			if (query.matches(el, 'textbox,button')) {
				ret = el.value || el.textContent;
			} else if (query.matches(el, 'combobox,listbox')) {
				var selected = query.querySelector(el, ':selected') || query.querySelector(el, 'option');
				if (selected) {
					ret = getName(selected, recursive, referenced, owned);
				}
			} else if (query.matches(el, 'range')) {
				ret = '' + (query.getAttribute(el, 'valuetext') || query.getAttribute(el, 'valuenow') || el.value);
			}
		}
	}

	// F
	// FIXME: menu is not mentioned in the spec
	if (!ret.trim() && (recursive || allowNameFromContent(el) || el.closest('label')) && !query.matches(el, 'menu')) {
		ret = getContent(el, referenced, owned);
	}

	// TODO: G
	// TODO: H

	// FIXME: not mentioned in the spec
	if (!ret.trim()) {
		for (var selector in constants.nameDefaults) {
			if (el.matches(selector)) {
				ret = constants.nameDefaults[selector];
			}
		}
	}

	// I
	if (!ret.trim()) {
		ret = el.title || '';
	}

	var before = getPseudoContent(el, ':before');
	var after = getPseudoContent(el, ':after');
	return before + ret + after;
};

var getNameTrimmed = function(el) {
	return getName(el).replace(/\s+/g, ' ').trim();
};

var getDescription = function(el) {
	var ret = '';
	var owned = [];

	if (el.matches('[aria-describedby]')) {
		var ids = el.getAttribute('aria-describedby').split(/\s+/);
		var strings = ids.map(function(id) {
			var label = document.getElementById(id);
			return label ? getName(label, true, label, owned) : '';
		});
		ret = strings.join(' ');
	} else if (el.title) {
		ret = el.title;
	} else if (el.placeholder) {
		ret = el.placeholder;
	}

	ret = (ret || '').trim().replace(/\s+/g, ' ');

	if (ret === getNameTrimmed(el)) {
		ret = '';
	}

	return ret;
};

module.exports = {
	getName: getNameTrimmed,
	getDescription: getDescription,
};
<|endoftext|>


<commit_before>import { EventTargetAssembler } from './eventtarget'

const { Node } = window
const {
    ATTRIBUTE_NODE,
    COMMENT_NODE,
    DOCUMENT_NODE,
    DOCUMENT_TYPE_NODE,
    DOCUMENT_FRAGMENT_NODE,
    ELEMENT_NODE,
    TEXT_NODE
} = Node
const { getTargetOf } = EventTargetAssembler
const NODE_PROPERTY_NAME = 'node'

/**
 * @see https://www.w3.org/TR/dom/#interface-node
 * @abstract
 */
export class NodeAssembler extends EventTargetAssembler {
    /**
     * @param {*} [init]
     * @override
     */
    create(init) {
        if(init && init.constructor === Object && init.hasOwnProperty(NODE_PROPERTY_NAME)) {
            this.node = init.node
        }
        else super.create(init)
    }

    /**
     * @param {boolean} deep
     * @returns {NodeAssembler|*}
     */
    cloneNode(deep) {
        return this.getInstanceOf(this.node.cloneNode(deep), this.constructor)
    }

    /**
     * @param {Node|NodeAssembler|*|null} node
     * @param {class} [assembler] - NodeAssembler subclass
     * @returns {NodeAssembler|*}
     */
    getInstanceOf(node, assembler) {
        return this.constructor.getInstanceOf(node, assembler)
    }

    /**
     * @param {NodeAssembler|Node} object
     * @returns {boolean}
     *
     */
    isEqualNode(object) {
        return this.node.isEqualNode(getNodeOf(object))
    }

    /**
     * @param {Node|*} node
     */
    set node(node) {
        if(node !== this.node) {
            if(node instanceof this.constructor.interface) {
                this.setInstanceOf(node)
            }
            else throw TypeError(`Failed to set 'node' on '${ this.constructor.name }': parameter is not of expected type.`)
        }
    }

    /**
     * @returns {Node|*}
     */
    get node() {
        return getTargetOf(this)
    }

    /**
     * @returns {DocumentAssembler}
     */
    get ownerDocument() {
        return this.getInstanceOf(this.node.ownerDocument)
    }

    /**
     * @abstract
     * @override
     */
    static create() {
        throw TypeError(`Could not create an abstract ${ this.interface.name } instance`)
    }

    /**
     * @param {Node|*} node
     * @returns {class|*}
     */
    static getAssemblerOf(node) {
        switch(node.nodeType) {
            case ATTRIBUTE_NODE :
                return this.attrAssembler.resolve(node)
            case COMMENT_NODE :
                return this.commentAssembler.resolve(node)
            case DOCUMENT_NODE :
                return this.documentAssembler.resolve(node)
            case DOCUMENT_TYPE_NODE :
                return this.doctypeAssembler.resolve(node)
            case DOCUMENT_FRAGMENT_NODE :
                return this.documentFragmentAssembler.resolve(node)
            case ELEMENT_NODE :
                return this.elementAssembler.resolve(node)
            case TEXT_NODE :
                return this.textAssembler.resolve(node)
            default :
                return NodeAssembler.ChildNodeAssembler
        }
    }

    /**
     * @param {Node|NodeAssembler|*|null} node
     * @param {class} [assembler]
     * @returns {NodeAssembler|*}
     */
    static getInstanceOf(node, assembler) {
        if(node) {
            const instance = super.getInstanceOf(node)
            if(instance) {
                return instance
            }
            if(!assembler) {
                assembler = this.getAssemblerOf(node)
            }
            return new assembler({ node })
        }
        return null
    }

    /**
     * @param {NodeAssembler|Node|*} object
     * @returns {*}
     */
    static getNodeOf(object) {
        return object instanceof NodeAssembler?
            object.node :
            object
    }

    /**
     * @param {Node|*} node
     * @returns {NodeAssembler|*}
     * @abstract
     */
    static resolve(node) {
        return this
    }

    /**
     * @returns {class|*} AttrAssembler
     */
    static get attrAssembler() {
        return NodeAssembler.AttrAssembler
    }

    /**
     * @returns {class|*} CommentAssembler or ChildNodeAssembler
     */
    static get commentAssembler() {
        return NodeAssembler.CommentAssembler || NodeAssembler.ChildNodeAssembler
    }

    /**
     * @returns {class|*} DocumentTypeAssembler or ChildNodeAssembler
     */
    static get doctypeAssembler() {
        return NodeAssembler.DocumentTypeAssembler || NodeAssembler.ChildNodeAssembler
    }

    /**
     * @returns {class|*} DocumentAssembler or ParentNodeAssembler
     */
    static get documentAssembler() {
        return NodeAssembler.DocumentAssembler || NodeAssembler.ParentNodeAssembler
    }

    /**
     * @returns {class|*} DocumentFragmentAssembler or ParentNodeAssembler
     */
    static get documentFragmentAssembler() {
        return NodeAssembler.DocumentFragmentAssembler || NodeAssembler.ParentNodeAssembler
    }

    /**
     * @returns {class|*} ElementAssembler
     */
    static get elementAssembler() {
        return NodeAssembler.ElementAssembler
    }

    /**
     * @returns {class|*} TextAssembler or ChildNodeAssembler
     */
    static get textAssembler() {
        return NodeAssembler.TextAssembler || NodeAssembler.ChildNodeAssembler
    }

    /**
     * @returns {interface} Node
     * @override
     */
    static get interface() {
        return Node
    }
}

const { getNodeOf } = NodeAssembler
<commit_msg>NodeAssembler: get rid of the `create` static method redefinition, simplify the `node` setter<commit_after>import { EventTargetAssembler } from './eventtarget'

const { Node } = window
const {
    ATTRIBUTE_NODE,
    COMMENT_NODE,
    DOCUMENT_NODE,
    DOCUMENT_TYPE_NODE,
    DOCUMENT_FRAGMENT_NODE,
    ELEMENT_NODE,
    TEXT_NODE
} = Node
const { getTargetOf } = EventTargetAssembler
const NODE_PROPERTY_NAME = 'node'

/**
 * @see https://www.w3.org/TR/dom/#interface-node
 * @abstract
 */
export class NodeAssembler extends EventTargetAssembler {
    /**
     * @param {*} [init]
     * @override
     */
    create(init) {
        if(init && init.constructor === Object && init.hasOwnProperty(NODE_PROPERTY_NAME)) {
            this.node = init.node
        }
        else super.create(init)
    }

    /**
     * @param {boolean} deep
     * @returns {NodeAssembler|*}
     */
    cloneNode(deep) {
        return this.getInstanceOf(this.node.cloneNode(deep), this.constructor)
    }

    /**
     * @param {Node|NodeAssembler|*|null} node
     * @param {class} [assembler] - NodeAssembler subclass
     * @returns {NodeAssembler|*}
     */
    getInstanceOf(node, assembler) {
        return this.constructor.getInstanceOf(node, assembler)
    }

    /**
     * @param {NodeAssembler|Node} object
     * @returns {boolean}
     *
     */
    isEqualNode(object) {
        return this.node.isEqualNode(getNodeOf(object))
    }

    /**
     * @param {Node|*} node
     */
    set node(node) {
        if(node !== this.node) {
            this.setInstanceOf(node)
        }
    }

    /**
     * @returns {Node|*}
     */
    get node() {
        return getTargetOf(this)
    }

    /**
     * @returns {DocumentAssembler}
     */
    get ownerDocument() {
        return this.getInstanceOf(this.node.ownerDocument)
    }

    /**
     * @param {Node|*} node
     * @returns {class|*}
     */
    static getAssemblerOf(node) {
        switch(node.nodeType) {
            case ATTRIBUTE_NODE :
                return this.attrAssembler.resolve(node)
            case COMMENT_NODE :
                return this.commentAssembler.resolve(node)
            case DOCUMENT_NODE :
                return this.documentAssembler.resolve(node)
            case DOCUMENT_TYPE_NODE :
                return this.doctypeAssembler.resolve(node)
            case DOCUMENT_FRAGMENT_NODE :
                return this.documentFragmentAssembler.resolve(node)
            case ELEMENT_NODE :
                return this.elementAssembler.resolve(node)
            case TEXT_NODE :
                return this.textAssembler.resolve(node)
            default :
                return NodeAssembler.ChildNodeAssembler
        }
    }

    /**
     * @param {Node|NodeAssembler|*|null} node
     * @param {class} [assembler]
     * @returns {NodeAssembler|*}
     */
    static getInstanceOf(node, assembler) {
        if(node) {
            const instance = super.getInstanceOf(node)
            if(instance) {
                return instance
            }
            if(!assembler) {
                assembler = this.getAssemblerOf(node)
            }
            return new assembler({ node })
        }
        return null
    }

    /**
     * @param {NodeAssembler|Node|*} object
     * @returns {*}
     */
    static getNodeOf(object) {
        return object instanceof NodeAssembler?
            object.node :
            object
    }

    /**
     * @param {Node|*} node
     * @returns {NodeAssembler|*}
     * @abstract
     */
    static resolve(node) {
        return this
    }

    /**
     * @returns {class|*} AttrAssembler
     */
    static get attrAssembler() {
        return NodeAssembler.AttrAssembler
    }

    /**
     * @returns {class|*} CommentAssembler or ChildNodeAssembler
     */
    static get commentAssembler() {
        return NodeAssembler.CommentAssembler || NodeAssembler.ChildNodeAssembler
    }

    /**
     * @returns {class|*} DocumentTypeAssembler or ChildNodeAssembler
     */
    static get doctypeAssembler() {
        return NodeAssembler.DocumentTypeAssembler || NodeAssembler.ChildNodeAssembler
    }

    /**
     * @returns {class|*} DocumentAssembler or ParentNodeAssembler
     */
    static get documentAssembler() {
        return NodeAssembler.DocumentAssembler || NodeAssembler.ParentNodeAssembler
    }

    /**
     * @returns {class|*} DocumentFragmentAssembler or ParentNodeAssembler
     */
    static get documentFragmentAssembler() {
        return NodeAssembler.DocumentFragmentAssembler || NodeAssembler.ParentNodeAssembler
    }

    /**
     * @returns {class|*} ElementAssembler
     */
    static get elementAssembler() {
        return NodeAssembler.ElementAssembler
    }

    /**
     * @returns {class|*} TextAssembler or ChildNodeAssembler
     */
    static get textAssembler() {
        return NodeAssembler.TextAssembler || NodeAssembler.ChildNodeAssembler
    }

    /**
     * @returns {interface} Node
     * @override
     */
    static get interface() {
        return Node
    }
}

const { getNodeOf } = NodeAssembler
<|endoftext|>


<commit_before>var StateEventEmitter = require('./util/state-eventemitter'),
    Deferred          = require('./util/deferred'),
    Crypto            = require('./util/crypto'),
    PeerArray         = require('./util/peerarray'),

    globals           = require('./globals.js'),

    RoutingTable      = require('./dht/routing-table'),
    Peer              = require('./dht/peer'),
    BootstrapPeer     = require('./dht/bootstrap-peer'),
    IterativeFind     = require('./dht/iterativefind'),

    Reactor           = require('./network/reactor'),

    ValueManagement   = require('./data/value-store');

var Node = module.exports = Peer.extend({
  /**
   * TODO : explicit the options..
   *
   *
   * @param  {[type]} id      [description]
   * @param  {[type]} options [description]
   * @return {[type]}
   */
  initialize: function(id, options) {
    // extends Peer
    this.supr('non-defined', id || Crypto.digest.randomSHA1());

    //implements StateEventEmitter
    for (var fn in StateEventEmitter.prototype) {
      if (fn !== 'initialize') this[fn] = StateEventEmitter.prototype[fn];
    }
    StateEventEmitter.prototype.initialize.call(this);

    this.setState('initializing');

    // store config
    var config = this.config = {};
    for (var option in options) {
      config[option] = options[option];
    }

    // extracts bootstraps from the config object
    if (!Array.isArray(config.bootstraps) || config.bootstraps.length === 0) {
      throw new Error('no bootstrap to join the network');
    } else {
      this._bootstraps = config.bootstraps.map(function(address) {
        return new BootstrapPeer(address);
      });
    }

    // instantiate a routing table and listen to it
    this._routingTable = new RoutingTable(this, config.routing_table);
    this._routingTable.on(this.routingTableEvents, this);

    // instantiate a reactor and listen to it
    this._reactor = new Reactor(this, config.reactor);
    this._reactor.on(this.reactorEvents, this);

    this.setState('initialized');
  },

  /**
   * Connect method : make the reactor connect.
   * @public
   *
   * @param  {Function} [callback] - callback to be called when connected
   * @param  {Object}   [context = this] - context of the callback
   * @return {self}
   */
  connect: function(callback, context) {
    if (this.stateIsNot('connected')) {
      if (callback) {
        this.once('connected', callback, context || this);
      }
      this._reactor.connectTransport();
    }
    return this;
  },

  /**
   * Disconnect method : make the reactor disconnect.
   * @public
   *
   * @param  {Function} [callback] - callback to be called when connected
   * @param  {Object}   [context = this] - context of the callback
   * @return {self}
   */
  disconnect: function(callback, context) {
    if (this.stateIsNot('disconnected')) {
      if (callback) {
        this.once('disconnected', callback, context || this);
      }
      this._routingTable.stop();
      this._reactor.disconnectTransport();
    }
    return this;
  },

  /**
   * Joining process : do an iterative find node on our own ID,
   * startying by contacting the peers passed as bootstraps.
   * @public
   *
   * TODO : expose a public API.
   *
   * @param  {Function} callback   - called when bootstraps process ends
   * @param  {Object}   context    - context of the callback
   *
   * @return {self} this
   */
  join: function(callback, context) {
    // lookup process
    var startLookup = function() {
      var deferred = new Deferred();
      this.emit('joining');
      this.iterativeFindNode(this)
          .addCallback(function() {
            deferred.reject(new Error('found an other node with same ID'));
          })
          .addErrback(function(reached) {
            if (reached.size() >= globals.K) {
              deferred.resolve();
            } else {
              deferred.reject(new Error('not enough peers'));
            }
          });
      return deferred;
    };
    var noBootstrap = function() {
      return new Error('no bootstrap');
    };

    // joining result
    var success = function() {
      this.emit('joined');
    };
    var failure = function() {
      this.emit('join failed');
    };

    context = context || this;
    Deferred.whenAtLeast(this._reactor.sendRPCs(this._bootstraps, 'PING'))
            .pipe(startLookup, noBootstrap, this)
            .then(success, failure, this)
            .then(callback, callback, context);

    return this;
  },

  /**
   * Get a value on the DHT providing the associated key.
   *
   * @public
   * Public wrapper around #iterativeFindValue.
   *
   * Provided callback will be called with the found value
   * or with null if not found.
   *
   * @param {String}   key       - key to find
   * @param {Function} callback  - function to be called at end
   * @param {Object}   [context] - context of callback
   * @return {self}
   */
  get: function(key, callback, context) {
    context = context || this;
    this.iterativeFindValue(key).then(
      function(kv) {
        callback.call(context, kv.value);
      }, function() {
        callback.call(context, null);
      });
    return this;
  },

  /**
   * Put a given value on the DHT associated to the given key and
   * with the given expiration time.
   *
   * @public
   * Public wrapper around #iterativeStore.
   *
   * If the given key is `null` the associated key is set to the SHA1 of
   * the value. Expiration time is not mandatory and set to infinite
   * by default.
   *
   * An optional callback (with a context) can be provided and will be
   * called with :
   *   - 1st parameter : key associated to the value on the DHT
   *   - 2nd parameter : number of peers that successfully stored the
   *        value. If 0, the process has failed.
   *
   * @param {String || null}  key        - key or null if value by default SHA1(value)
   * @param {*}               value      - value to store on the DHT
   * @param {Date || Number}  [key]      - date of expiration of the key/value
   * @param {Function}        [callback] - callback when the store process ends
   * @param {Object}          [context]  - context of callback
   * @return {self}
   */
  put: function(key, value, exp, callback, context) {
    // if no exp, arguments sliding
    if (typeof exp == 'function') {
      exp = undefined;
      callback = exp;
      context = callback;
    }

    // default values
    key = key || Crypto.digest.SHA1(String(value));
    exp = exp || -1;
    context = context || this;

    this.iterativeStore(key, value, exp)
        .then(function(key, peers) {
          if (callback) callback.call(context, key, peers.size());
        }, function() {
          if (callback) callback.call(context, null, 0);
        });
    return this;
  },

 //# INTERNAL EVENTS HANDLING

  /**
   * Reactions to events coming from the reactor.
   * @type {Object}
   */
  reactorEvents : {

    /**
     * On `connected` event :
     *   - save our address on the network
     *   - state is `connected`
     *
     * @param  {String} address - Address of the node on the network
     */
    connected: function(address) {
      this.setAddress(address);
      if (typeof this._store == 'undefined') {
        this._store = new ValueManagement(this, this.config.value_management);
        this._store.on(this.VMEvents, this);
      }
      this.setState('connected');
    },

    /**
     * On `disconnected`, state becomes `disconnected`.
     */
    disconnected: function() {
      this.setState('disconnected');
    },

    /**
     * On `reached` peer, add it to routing table.
     *
     * @param  {Peer} peer - Peer that had been reached.
     */
    reached: function(peer) {
      peer.touch();
      this._routingTable.addPeer(peer);
    },

    /**
     * On `queried` (means we received a RPC request), call
     * the appopriate method to fullfill the RPC.
     * @see #handle+`method_name`
     *
     * @param  {RPC} rpc - The received rpc
     */
    queried: function(rpc) {
      if (!rpc.inProgress())
        return;
      this['handle' + rpc.getMethod()].call(this, rpc);
    },

    /**
     * On `outdated` (means we received a response from a peer
     * which ID seems to be different from the one in the routing table),
     * update the ID in the routing table.
     *
     * @param  {Peer} peer - outdated peer
     * @param  {String} id - new id
     */
    outdated: function(peer, id) {
      this._routingTable.removePeer(peer);
      peer.setID(id);
      this._routingTable.addPeer(peer);
    }
  },

  /**
   * Handle an incoming PING RPC request :
   * simply respond to it.
   *
   * @param  {PingRPC} rpc - the incoming RPC object
   */
  handlePING: function(rpc) {
    rpc.resolve();
  },

  /**
   * Handle an incoming FIND_NODE RPC request :
   * fetch from the routing table the BETA closest
   * peers (except the querying peer) to the
   * targeted ID and respond to the rpc.
   *
   * @param  {FindNodeRPC} rpc - the inconming rpc object
   */
  handleFIND_NODE: function(rpc) {
    rpc.resolve(this._routingTable.getClosePeers(rpc.getTarget(), globals.BETA, rpc.getQuerying()));
  },

  /**
   * Handle an incoming FIND_VALUE request:
   * - if we got the value, respond it.
   * - if not, fetch the BETA closest peer, respond them.
   *
   * @param  {FindValueRPC} rpc - the incoming rpc oject
   */
  handleFIND_VALUE: function(rpc) {
    this._store.retrieve(rpc.getTarget())
        .then(function(value, exp) {
          rpc.resolve({value : value, exp : exp}, true);
        }, function() {
          rpc.resolve(this._routingTable.getClosePeers(rpc.getTarget(), globals.BETA, rpc.getQuerying()), false);
        }, this);
  },

  /**
   * Handle an incoming STORE request :
   * store the value in ValueManagement and respond.
   *
   * @param  {StoreRPC} rpc - the incoming rpc object.
   */
  handleSTORE: function(rpc) {
    this._store.save(rpc.getKey(), rpc.getValue(), rpc.getExpiration())
               .then(rpc.resolve, rpc.reject, rpc);
  },

  /**
   * Reactions to events comming from the routing table.
   * @type {Object}
   */
  routingTableEvents : {

    /**
     * On `refresh` (means a kbucket has not seen any fresh
     * peers for a REFRESH_TIMEOUT time), do an titerative find node
     * on a random ID in the KBucket range.
     *
     * @param  {KBucket} kbucket - Kbucket needing to be refreshed
     */
    refresh: function(kbucket) {
      var random_sha = Crypto.digest.randomSHA1(this.getID(), kbucket.getRange());
      this.iterativeFindNode(random_sha);
    }
  },

  /**
   * Reactions to event coming from the value management.
   * @type {Object}
   */
  VMEvents : {

    /**
     * On `republish` (means a key-value needs to be republished
     * on the network) : do an iterative store on it.
     *
     * @param  {String} key   - key
     * @param  {Object} value - value
     * @param  {Date | Number} exp   - expiration date
     */
    republish: function(key, value, exp) {
      this.iterativeStore(key, value, exp);
    }
  },

 //# ITERATIVE PROCESSES

  /**
   * Launch an iterative find node process.
   *
   * Return a deffered object :
   *   - resolve :
   *       {Peer}      peer  - peer found that have the targeted ID
   *       {PeerArray} peers - reached peers during the iterative process
   *   - reject :
   *       {PeerArray} peers - reached peers during the iterative process
   *
   * @param  {Peer | String} peer - Peer or Node ID to find.
   * @return {Deferred}
   */
  iterativeFindNode: function(peer) {
    return this._iterativeFind(peer, 'NODE');
  },

  /**
   * Launch an iterative find value process.
   *
   * If succeed, it STOREs the value to the
   * closest reached peer which didn't responded
   * the value.
   *
   * Return a deffered object :
   *   - resolve :
   *       {Object}    keyValue - properties :
   *                                `value` - the retrieved value
   *                                `exp`   - the expiration date
   *       {PeerArray} peers    - reached peers during the iterative process
   *   - reject
   *       {PeerArray} peers - reached peers during the iterative process
   *
   * @param  {String} key - targeted ID
   * @return {Deferred}
   */
  iterativeFindValue: function(key) {
    var reactor = this._reactor;
    return this._iterativeFind(key, 'VALUE')
               .addCallback(function(result, reached) {
                 if (reached.size() > 1) {
                   var index = (reached.newClosestIndex() > 0) ? 0 : 1;
                   reactor.STORE(reached.getPeer(index), key, result.value, result.exp);
                 }
               });
  },

  /**
   * Launch an iterative store process :
   *   do an iterative find node on the key,
   *   and send STORE RPCsto the K closest
   *   reached peers.
   *
   * Return a deferred object that is resolved when
   * at least one of the store RPC is resolved :
   *   - resolve :
   *       {String}    key   - key with wihich the value was stored
   *       {PeerArray} peers - peers that resolved the store RPC
   *       {PeerArray} peers_not - peers that did not resolved the store RPC
   *   - reject
   *       {PeerArray} peers_not - peers that did not resolved the store RPC
   *
   * @param  {String} key - key
   * @param  {*} value - value to store on the network
   * @param  {Date | Integer} [exp = never] - experiation date of the value
   * @return {Deferred}
   */
  iterativeStore: function(key, value, exp) {
    if (!globals.REGEX_NODE_ID.test(key)) {
      throw new TypeError('non valid key');
    }

    var def = new Deferred();

    var _stores = function(peers) {
      var targets = peers.pickOutFirst(globals.K);
      Deferred.whenAtLeast(this._reactor.sendRPCs(targets, 'STORE', key, value, exp), 1)
              .then(function(stored, not_stored) {
                def.resolve(
                  key,
                  new PeerArray(stored.map(function(rpc) {
                    return rpc.getQueried();
                  })),
                  new PeerArray(not_stored.map(function(rpc) {
                    return rpc.getQueried();
                  }))
                );
              }, function(stored, not_stored) {
                def.reject(new PeerArray(not_stored.map(function(rpc) {
                  return rpc.getQueried();
                })));
              });
    };

    this.iterativeFindNode(key).then(
      function(peer, peers) {
        _stores.call(this, peers);
      },
      function(peers) {
        _stores.call(this, peers);
      }, this);

    return def;
  },

  /**
   * Iterative lookup used by kademlia
   * See /doc papers for informations on the loose parallelism
   * used in this implementation.
   *
   * @param {String} id   - Identifier of the peer or objects
   * @param {String} type - the type of the lookup
   * @return {Deferred}
   */
  _iterativeFind: function(peer, type) {
    var lookup = new IterativeFind(this, peer, type);
    var peers  = this._routingTable.getClosePeers(peer, globals.K);
    this.emit('iterativeFind started', lookup, peers);
    lookup.startWith(peers);
    return lookup;
  }
});<commit_msg>adapt node to new iterative-deferred<commit_after>var StateEventEmitter  = require('./util/state-eventemitter'),
    Deferred           = require('./util/deferred'),
    Crypto             = require('./util/crypto'),
    PeerArray          = require('./util/peerarray'),

    globals            = require('./globals.js'),

    RoutingTable       = require('./dht/routing-table'),
    Peer               = require('./dht/peer'),
    BootstrapPeer      = require('./dht/bootstrap-peer'),
    //IterativeFind      = require('./dht/iterativefind'),
    IterativeFindValue = require('./dht/iterativefind/iterative-findvalue'),
    IterativeFindNode  = require('./dht/iterativefind/iterative-findnode'),

    Reactor            = require('./network/reactor'),

    ValueManagement    = require('./data/value-store');

var Node = module.exports = Peer.extend({
  /**
   * TODO : explicit the options..
   *
   *
   * @param  {[type]} id      [description]
   * @param  {[type]} options [description]
   * @return {[type]}
   */
  initialize: function(id, options) {
    // extends Peer
    this.supr('non-defined', id || Crypto.digest.randomSHA1());

    //implements StateEventEmitter
    for (var fn in StateEventEmitter.prototype) {
      if (fn !== 'initialize') this[fn] = StateEventEmitter.prototype[fn];
    }
    StateEventEmitter.prototype.initialize.call(this);

    this.setState('initializing');

    // store config
    var config = this.config = {};
    for (var option in options) {
      config[option] = options[option];
    }

    // extracts bootstraps from the config object
    if (!Array.isArray(config.bootstraps) || config.bootstraps.length === 0) {
      throw new Error('no bootstrap to join the network');
    } else {
      this._bootstraps = config.bootstraps.map(function(address) {
        return new BootstrapPeer(address);
      });
    }

    // instantiate a routing table and listen to it
    this._routingTable = new RoutingTable(this, config.routing_table);
    this._routingTable.on(this.routingTableEvents, this);

    // instantiate a reactor and listen to it
    this._reactor = new Reactor(this, config.reactor);
    this._reactor.on(this.reactorEvents, this);

    this.setState('initialized');
  },

  /**
   * Connect method : make the reactor connect.
   * @public
   *
   * @param  {Function} [callback] - callback to be called when connected
   * @param  {Object}   [context = this] - context of the callback
   * @return {self}
   */
  connect: function(callback, context) {
    if (this.stateIsNot('connected')) {
      if (callback) {
        this.once('connected', callback, context || this);
      }
      this._reactor.connectTransport();
    }
    return this;
  },

  /**
   * Disconnect method : make the reactor disconnect.
   * @public
   *
   * @param  {Function} [callback] - callback to be called when connected
   * @param  {Object}   [context = this] - context of the callback
   * @return {self}
   */
  disconnect: function(callback, context) {
    if (this.stateIsNot('disconnected')) {
      if (callback) {
        this.once('disconnected', callback, context || this);
      }
      this._routingTable.stop();
      this._reactor.disconnectTransport();
    }
    return this;
  },

  /**
   * Joining process : do an iterative find node on our own ID,
   * startying by contacting the peers passed as bootstraps.
   * @public
   *
   * TODO : expose a public API.
   *
   * @param  {Function} callback   - called when bootstraps process ends
   * @param  {Object}   context    - context of the callback
   *
   * @return {self} this
   */
  join: function(callback, context) {
    // lookup process
    var startLookup = function() {
      var deferred = new Deferred();
      this.emit('joining');
      this.iterativeFindNode(this)
          .addCallback(function() {
            deferred.reject(new Error('found an other node with same ID'));
          })
          .addErrback(function(reached) {
            if (reached.size() >= globals.K) {
              deferred.resolve();
            } else {
              deferred.reject(new Error('not enough peers'));
            }
          });
      return deferred;
    };
    var noBootstrap = function() {
      return new Error('no bootstrap');
    };

    // joining result
    var success = function() {
      this.emit('joined');
    };
    var failure = function() {
      this.emit('join failed');
    };

    context = context || this;
    Deferred.whenAtLeast(this._reactor.sendRPCs(this._bootstraps, 'PING'))
            .pipe(startLookup, noBootstrap, this)
            .then(success, failure, this)
            .then(callback, callback, context);

    return this;
  },

  /**
   * Get a value on the DHT providing the associated key.
   *
   * @public
   * Public wrapper around #iterativeFindValue.
   *
   * Provided callback will be called with the found value
   * or with null if not found.
   *
   * @param {String}   key       - key to find
   * @param {Function} callback  - function to be called at end
   * @param {Object}   [context] - context of callback
   * @return {self}
   */
  get: function(key, callback, context) {
    context = context || this;
    this.iterativeFindValue(key).then(
      function(kv) {
        callback.call(context, kv.value);
      }, function() {
        callback.call(context, null);
      });
    return this;
  },

  /**
   * Put a given value on the DHT associated to the given key and
   * with the given expiration time.
   *
   * @public
   * Public wrapper around #iterativeStore.
   *
   * If the given key is `null` the associated key is set to the SHA1 of
   * the value. Expiration time is not mandatory and set to infinite
   * by default.
   *
   * An optional callback (with a context) can be provided and will be
   * called with :
   *   - 1st parameter : key associated to the value on the DHT
   *   - 2nd parameter : number of peers that successfully stored the
   *        value. If 0, the process has failed.
   *
   * @param {String || null}  key        - key or null if value by default SHA1(value)
   * @param {*}               value      - value to store on the DHT
   * @param {Date || Number}  [key]      - date of expiration of the key/value
   * @param {Function}        [callback] - callback when the store process ends
   * @param {Object}          [context]  - context of callback
   * @return {self}
   */
  put: function(key, value, exp, callback, context) {
    // if no exp, arguments sliding
    if (typeof exp == 'function') {
      exp = undefined;
      callback = exp;
      context = callback;
    }

    // default values
    key = key || Crypto.digest.SHA1(String(value));
    exp = exp || -1;
    context = context || this;

    this.iterativeStore(key, value, exp)
        .then(function(key, peers) {
          if (callback) callback.call(context, key, peers.size());
        }, function() {
          if (callback) callback.call(context, null, 0);
        });
    return this;
  },

 //# INTERNAL EVENTS HANDLING

  /**
   * Reactions to events coming from the reactor.
   * @type {Object}
   */
  reactorEvents : {

    /**
     * On `connected` event :
     *   - save our address on the network
     *   - state is `connected`
     *
     * @param  {String} address - Address of the node on the network
     */
    connected: function(address) {
      this.setAddress(address);
      if (typeof this._store == 'undefined') {
        this._store = new ValueManagement(this, this.config.value_management);
        this._store.on(this.VMEvents, this);
      }
      this.setState('connected');
    },

    /**
     * On `disconnected`, state becomes `disconnected`.
     */
    disconnected: function() {
      this.setState('disconnected');
    },

    /**
     * On `reached` peer, add it to routing table.
     *
     * @param  {Peer} peer - Peer that had been reached.
     */
    reached: function(peer) {
      peer.touch();
      this._routingTable.addPeer(peer);
    },

    /**
     * On `queried` (means we received a RPC request), call
     * the appopriate method to fullfill the RPC.
     * @see #handle+`method_name`
     *
     * @param  {RPC} rpc - The received rpc
     */
    queried: function(rpc) {
      if (!rpc.inProgress())
        return;
      this['handle' + rpc.getMethod()].call(this, rpc);
    },

    /**
     * On `outdated` (means we received a response from a peer
     * which ID seems to be different from the one in the routing table),
     * update the ID in the routing table.
     *
     * @param  {Peer} peer - outdated peer
     * @param  {String} id - new id
     */
    outdated: function(peer, id) {
      this._routingTable.removePeer(peer);
      peer.setID(id);
      this._routingTable.addPeer(peer);
    }
  },

  /**
   * Handle an incoming PING RPC request :
   * simply respond to it.
   *
   * @param  {PingRPC} rpc - the incoming RPC object
   */
  handlePING: function(rpc) {
    rpc.resolve();
  },

  /**
   * Handle an incoming FIND_NODE RPC request :
   * fetch from the routing table the BETA closest
   * peers (except the querying peer) to the
   * targeted ID and respond to the rpc.
   *
   * @param  {FindNodeRPC} rpc - the inconming rpc object
   */
  handleFIND_NODE: function(rpc) {
    rpc.resolve(this._routingTable.getClosePeers(rpc.getTarget(), globals.BETA, rpc.getQuerying()));
  },

  /**
   * Handle an incoming FIND_VALUE request:
   * - if we got the value, respond it.
   * - if not, fetch the BETA closest peer, respond them.
   *
   * @param  {FindValueRPC} rpc - the incoming rpc oject
   */
  handleFIND_VALUE: function(rpc) {
    this._store.retrieve(rpc.getTarget())
        .then(function(value, exp) {
          rpc.resolve({value : value, exp : exp}, true);
        }, function() {
          rpc.resolve(this._routingTable.getClosePeers(rpc.getTarget(), globals.BETA, rpc.getQuerying()), false);
        }, this);
  },

  /**
   * Handle an incoming STORE request :
   * store the value in ValueManagement and respond.
   *
   * @param  {StoreRPC} rpc - the incoming rpc object.
   */
  handleSTORE: function(rpc) {
    this._store.save(rpc.getKey(), rpc.getValue(), rpc.getExpiration())
               .then(rpc.resolve, rpc.reject, rpc);
  },

  /**
   * Reactions to events comming from the routing table.
   * @type {Object}
   */
  routingTableEvents : {

    /**
     * On `refresh` (means a kbucket has not seen any fresh
     * peers for a REFRESH_TIMEOUT time), do an titerative find node
     * on a random ID in the KBucket range.
     *
     * @param  {KBucket} kbucket - Kbucket needing to be refreshed
     */
    refresh: function(kbucket) {
      var random_sha = Crypto.digest.randomSHA1(this.getID(), kbucket.getRange());
      this.iterativeFindNode(random_sha);
    }
  },

  /**
   * Reactions to event coming from the value management.
   * @type {Object}
   */
  VMEvents : {

    /**
     * On `republish` (means a key-value needs to be republished
     * on the network) : do an iterative store on it.
     *
     * @param  {String} key   - key
     * @param  {Object} value - value
     * @param  {Date | Number} exp   - expiration date
     */
    republish: function(key, value, exp) {
      this.iterativeStore(key, value, exp);
    }
  },

 //# ITERATIVE PROCESSES

  /**
   * Launch an iterative find node process.
   *
   * Return a deffered object :
   *   - resolve :
   *       {Peer}      peer  - peer found that have the targeted ID
   *       {PeerArray} peers - reached peers during the iterative process
   *   - reject :
   *       {PeerArray} peers - reached peers during the iterative process
   *
   * @param  {Peer | String} peer - Peer or Node ID to find.
   * @return {Deferred}
   */
  iterativeFindNode: function(peer) {
    return this._iterativeFind(peer, 'NODE');
  },

  /**
   * Launch an iterative find value process.
   *
   * If succeed, it STOREs the value to the
   * closest reached peer which didn't responded
   * the value.
   *
   * Return a deffered object :
   *   - resolve :
   *       {Object}    keyValue - properties :
   *                                `value` - the retrieved value
   *                                `exp`   - the expiration date
   *       {PeerArray} peers    - reached peers during the iterative process
   *   - reject
   *       {PeerArray} peers - reached peers during the iterative process
   *
   * @param  {String} key - targeted ID
   * @return {Deferred}
   */
  iterativeFindValue: function(key) {
    var reactor = this._reactor;
    return this._iterativeFind(key, 'VALUE')
               .addCallback(function(result, reached) {
                 if (reached.size() > 1) {
                   var index = (reached.newClosestIndex() > 0) ? 0 : 1;
                   reactor.STORE(reached.getPeer(index), key, result.value, result.exp);
                 }
               });
  },

  /**
   * Launch an iterative store process :
   *   do an iterative find node on the key,
   *   and send STORE RPCsto the K closest
   *   reached peers.
   *
   * Return a deferred object that is resolved when
   * at least one of the store RPC is resolved :
   *   - resolve :
   *       {String}    key   - key with wihich the value was stored
   *       {PeerArray} peers - peers that resolved the store RPC
   *       {PeerArray} peers_not - peers that did not resolved the store RPC
   *   - reject
   *       {PeerArray} peers_not - peers that did not resolved the store RPC
   *
   * @param  {String} key - key
   * @param  {*} value - value to store on the network
   * @param  {Date | Integer} [exp = never] - experiation date of the value
   * @return {Deferred}
   */
  iterativeStore: function(key, value, exp) {
    if (!globals.REGEX_NODE_ID.test(key)) {
      throw new TypeError('non valid key');
    }

    var def = new Deferred();

    var _stores = function(peers) {
      var targets = peers.pickOutFirst(globals.K);
      Deferred.whenAtLeast(this._reactor.sendRPCs(targets, 'STORE', key, value, exp), 1)
              .then(function(stored, not_stored) {
                def.resolve(
                  key,
                  new PeerArray(stored.map(function(rpc) {
                    return rpc.getQueried();
                  })),
                  new PeerArray(not_stored.map(function(rpc) {
                    return rpc.getQueried();
                  }))
                );
              }, function(stored, not_stored) {
                def.reject(new PeerArray(not_stored.map(function(rpc) {
                  return rpc.getQueried();
                })));
              });
    };

    this.iterativeFindNode(key).then(
      function(peer, peers) {
        _stores.call(this, peers);
      },
      function(peers) {
        _stores.call(this, peers);
      }, this);

    return def;
  },

  /**
   * Iterative lookup used by kademlia
   * See /doc papers for informations on the loose parallelism
   * used in this implementation.
   *
   * @param {String} id   - Identifier of the peer or objects
   * @param {String} type - the type of the lookup
   * @return {Deferred}
   */
  _iterativeFind: function(peer, type) {

    var reactor = this._reactor;
    var sendFn = function(rpc) {
      reactor.sendRPC(rpc);
    };

    var lookup;
    switch(type) {
      case 'NODE':
        lookup = new IterativeFindNode(peer, sendFn);
        break;
      case 'VALUE':
        lookup = new IterativeFindValue(peer, sendFn);
        break;
    }

    var peers  = this._routingTable.getClosePeers(peer, globals.K);
    this.emit('iterativeFind started', lookup, peers);
    lookup.startWith(peers);
    return lookup;
  }
});<|endoftext|>


<commit_before>"use strict";

var fs = require('fs');
var path = require('path');
var ncp = require('ncp');
var mkdirp = require('mkdirp');
var prompt = require("prompt");
var cwd = process.cwd();
var done = require('./done');
var commit = require('./commit');
var config = require('./config')();

prompt.message = "[MinoriWiki]";

var open = function(file) {
    var ed = require('child_process').spawn(
        config['custom'].editor, [
            file
        ], {
            stdio: 'inherit'
        });

    ed.on('exit', finalize, config);
};

var finalize = function() {
    prompt.start();
    prompt.get({
        properties: {
            message: {
                description: "Please describe your changes"
            }
        }
    }, function (err, result) {
        commit(cwd + '/' + config['dir'].source, result.message, true);
        done();
    });
};

module.exports = function(filename) {
    var file = path.resolve(cwd + '/' + config['dir'].source + '/' + filename + '.md');

    fs.stat(file, function(err, stat) {
        if (!err) {
            console.log('File exists - Opening...');
            open(file);
        } else if (err.code === 'ENOENT') {
            console.log('Creating: ' + file);
            // FIXME hard coded template
            var content = 'title: ' + filename + '\n' +
                'category: ' + config['custom'].category + '\n' +
                'time: ' + Date.now() + '\n' +
                '---\n' + 'Content of the page, **markdown** enabled.\n';
            mkdirp.sync(path.dirname(file), { mode: '0755' });
            fs.writeFile(file, content, {
                encoding: 'utf8'
            }, function(err) {
                if (err) {
                    console.error(err);
                    console.error('Error creating new page file, please check your Minori installation.');
                    process.exit(1);
                }
                open(file);
            });
        } else {
            console.error('Error stat file: ' + file + '\n' + err.code);
            process.exit(1);
        }
    });
};
<commit_msg>use variable instead of hard coded template<commit_after>"use strict";

var fs = require('fs');
var path = require('path');
var ncp = require('ncp');
var mkdirp = require('mkdirp');
var prompt = require("prompt");
var cwd = process.cwd();
var done = require('./done');
var commit = require('./commit');
var config = require('./config')();

var template = path.resolve(__dirname + '/../assets/templates/note.md');

prompt.message = "[MinoriWiki]";

var open = function(file) {
    var ed = require('child_process').spawn(
        config['custom'].editor, [
            file
        ], {
            stdio: 'inherit'
        });

    ed.on('exit', finalize, config);
};

var finalize = function() {
    prompt.start();
    prompt.get({
        properties: {
            message: {
                description: "Please describe your changes"
            }
        }
    }, function (err, result) {
        commit(cwd + '/' + config['dir'].source, result.message, true);
        done();
    });
};

module.exports = function(filename) {
    var file = path.resolve(cwd + '/' + config['dir'].source + '/' + filename + '.md');

    fs.stat(file, function(err, stat) {
        if (!err) {
            console.log('File exists - Opening...');
            open(file);
        } else if (err.code === 'ENOENT') {
            console.log('Creating: ' + file);
            var content = fs.readFileSync(template, 'utf8')
                .replace('[% title %]', filename)
                .replace('[% category %]', config['custom'].category)
                .replace('[% time %]', Date.now());

            mkdirp.sync(path.dirname(file), { mode: '0755' });
            fs.writeFile(file, content, {
                encoding: 'utf8'
            }, function(err) {
                if (err) {
                    console.error(err);
                    console.error('Error creating new page file, please check your Minori installation.');
                    process.exit(1);
                }
                open(file);
            });
        } else {
            console.error('Error stat file: ' + file + '\n' + err.code);
            process.exit(1);
        }
    });
};
<|endoftext|>