<commit_before>import clone from 'clone';
import equal from 'deep-equal';
import extend from 'extend';
import DeltaOp from 'quill-delta/lib/op';
import Parchment from 'parchment';
import Quill from '../core/quill';
import logger from '../core/logger';
import Module from '../core/module';

let debug = logger('quill:keyboard');

const SHORTKEY = /Mac/i.test(navigator.platform) ? 'metaKey' : 'ctrlKey';

class Keyboard extends Module {
  static match(evt, binding) {
    binding = normalize(binding);
    if (!!binding.shortKey !== evt[SHORTKEY] && binding.shortKey !== null) return false;
    if (['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].some(function(key) {
      return (key != SHORTKEY && !!binding[key] !== evt[key] && binding[key] !== null);
    })) {
      return false;
    }
    return binding.key === (evt.which || evt.keyCode);
  }

  constructor(quill, options) {
    super(quill, options);
    this.bindings = {};
    Object.keys(this.options.bindings).forEach((name) => {
      if (this.options.bindings[name]) {
        this.addBinding(this.options.bindings[name]);
      }
    });
    this.addBinding({ key: Keyboard.keys.ENTER, shiftKey: null }, handleEnter);
    this.addBinding({ key: Keyboard.keys.ENTER, metaKey: null, ctrlKey: null, altKey: null }, function() {});
    if (/Gecko/i.test(navigator.userAgent)) {
      // Need to handle delete and backspace for Firefox in the general case #1171
      this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: true }, handleBackspace);
      this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: true }, handleDelete);
    } else {
      this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: true, prefix: /^.?$/ }, handleBackspace);
      this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: true, suffix: /^.?$/ }, handleDelete);
    }
    this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: false }, handleDeleteRange);
    this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: false }, handleDeleteRange);
    if (/Trident/i.test(navigator.userAgent)) {
      this.addBinding({ key: Keyboard.keys.BACKSPACE, shortKey: true }, handleBackspace);
      this.addBinding({ key: Keyboard.keys.DELETE, shortKey: true }, handleDelete);
    }
    this.listen();
  }

  addBinding(key, context = {}, handler = {}) {
    let binding = normalize(key);
    if (binding == null || binding.key == null) {
      return debug.warn('Attempted to add invalid keyboard binding', binding);
    }
    if (typeof context === 'function') {
      context = { handler: context };
    }
    if (typeof handler === 'function') {
      handler = { handler: handler };
    }
    binding = extend(binding, context, handler);
    this.bindings[binding.key] = this.bindings[binding.key] || [];
    this.bindings[binding.key].push(binding);
  }

  listen() {
    this.quill.root.addEventListener('keydown', (evt) => {
      if (evt.defaultPrevented) return;
      let which = evt.which || evt.keyCode;
      let bindings = (this.bindings[which] || []).filter(function(binding) {
        return Keyboard.match(evt, binding);
      });
      if (bindings.length === 0) return;
      let range = this.quill.getSelection();
      if (range == null || !this.quill.hasFocus()) return;
      let [line, offset] = this.quill.scroll.line(range.index);
      let [leafStart, offsetStart] = this.quill.scroll.leaf(range.index);
      let [leafEnd, offsetEnd] = range.length === 0 ? [leafStart, offsetStart] : this.quill.scroll.leaf(range.index + range.length);
      let prefixText = leafStart instanceof Parchment.Text ? leafStart.value().slice(0, offsetStart) : '';
      let suffixText = leafEnd instanceof Parchment.Text ? leafEnd.value().slice(offsetEnd) : '';
      let curContext = {
        collapsed: range.length === 0,
        empty: range.length === 0 && line.length() <= 1,
        format: this.quill.getFormat(range),
        offset: offset,
        prefix: prefixText,
        suffix: suffixText
      };
      let prevented = bindings.some((binding) => {
        if (binding.collapsed != null && binding.collapsed !== curContext.collapsed) return false;
        if (binding.empty != null && binding.empty !== curContext.empty) return false;
        if (binding.offset != null && binding.offset !== curContext.offset) return false;
        if (Array.isArray(binding.format)) {
          // any format is present
          if (binding.format.every(function(name) {
            return curContext.format[name] == null;
          })) {
            return false;
          }
        } else if (typeof binding.format === 'object') {
          // all formats must match
          if (!Object.keys(binding.format).every(function(name) {
            if (binding.format[name] === true) return curContext.format[name] != null;
            if (binding.format[name] === false) return curContext.format[name] == null;
            return equal(binding.format[name], curContext.format[name]);
          })) {
            return false;
          }
        }
        if (binding.prefix != null && !binding.prefix.test(curContext.prefix)) return false;
        if (binding.suffix != null && !binding.suffix.test(curContext.suffix)) return false;
        return binding.handler.call(this, range, curContext) !== true;
      });
      if (prevented) {
        evt.preventDefault();
      }
    });
  }
}

Keyboard.keys = {
  BACKSPACE: 8,
  TAB: 9,
  ENTER: 13,
  ESCAPE: 27,
  LEFT: 37,
  UP: 38,
  RIGHT: 39,
  DOWN: 40,
  DELETE: 46
};

Keyboard.DEFAULTS = {
  bindings: {
    'bold'      : makeFormatHandler('bold'),
    'italic'    : makeFormatHandler('italic'),
    'underline' : makeFormatHandler('underline'),
    'indent': {
      // highlight tab or tab at beginning of list, indent or blockquote
      key: Keyboard.keys.TAB,
      format: ['blockquote', 'indent', 'list'],
      handler: function(range, context) {
        if (context.collapsed && context.offset !== 0) return true;
        this.quill.format('indent', '+1', Quill.sources.USER);
      }
    },
    'outdent': {
      key: Keyboard.keys.TAB,
      shiftKey: true,
      format: ['blockquote', 'indent', 'list'],
      // highlight tab or tab at beginning of list, indent or blockquote
      handler: function(range, context) {
        if (context.collapsed && context.offset !== 0) return true;
        this.quill.format('indent', '-1', Quill.sources.USER);
      }
    },
    'outdent backspace': {
      key: Keyboard.keys.BACKSPACE,
      collapsed: true,
      format: ['blockquote', 'indent', 'list'],
      offset: 0,
      handler: function(range, context) {
        if (context.format.indent != null) {
          this.quill.format('indent', '-1', Quill.sources.USER);
        } else if (context.format.blockquote != null) {
          this.quill.format('blockquote', false, Quill.sources.USER);
        } else if (context.format.list != null) {
          this.quill.format('list', false, Quill.sources.USER);
        }
      }
    },
    'indent code-block': makeCodeBlockHandler(true),
    'outdent code-block': makeCodeBlockHandler(false),
    'remove tab': {
      key: Keyboard.keys.TAB,
      shiftKey: true,
      collapsed: true,
      prefix: /\t$/,
      handler: function(range) {
        this.quill.deleteText(range.index - 1, 1, Quill.sources.USER);
      }
    },
    'tab': {
      key: Keyboard.keys.TAB,
      handler: function(range, context) {
        if (!context.collapsed) {
          this.quill.scroll.deleteAt(range.index, range.length);
        }
        this.quill.insertText(range.index, '\t', Quill.sources.USER);
        this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
      }
    },
    'list empty enter': {
      key: Keyboard.keys.ENTER,
      collapsed: true,
      format: ['list'],
      empty: true,
      handler: function(range, context) {
        this.quill.format('list', false, Quill.sources.USER);
        if (context.format.indent) {
          this.quill.format('indent', false, Quill.sources.USER);
        }
      }
    },
    'checklist enter': {
      key: Keyboard.keys.ENTER,
      collapsed: true,
      format: { list: 'checked' },
      handler: function(range) {
        this.quill.scroll.insertAt(range.index, '\n');
        let [line, ] = this.quill.scroll.line(range.index + 1);
        line.format('list', 'unchecked');
        this.quill.update(Quill.sources.USER);
        this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
        this.quill.selection.scrollIntoView();
      }
    },
    'header enter': {
      key: Keyboard.keys.ENTER,
      collapsed: true,
      format: ['header'],
      suffix: /^$/,
      handler: function(range) {
        this.quill.scroll.insertAt(range.index, '\n');
        this.quill.formatText(range.index + 1, 1, 'header', false, Quill.sources.USER);
        this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
        this.quill.selection.scrollIntoView();
      }
    },
    'list autofill': {
      key: ' ',
      collapsed: true,
      format: { list: false },
      prefix: /^(1\.|-)$/,
      handler: function(range, context) {
        let length = context.prefix.length;
        this.quill.scroll.deleteAt(range.index - length, length);
        this.quill.formatLine(range.index - length, 1, 'list', length === 1 ? 'bullet' : 'ordered', Quill.sources.USER);
        this.quill.setSelection(range.index - length, Quill.sources.SILENT);
      }
    }
  }
};

function handleBackspace(range, context) {
  if (range.index === 0) return;
  let [line, ] = this.quill.scroll.line(range.index);
  let formats = {};
  if (context.offset === 0) {
    let curFormats = line.formats();
    let prevFormats = this.quill.getFormat(range.index-1, 1);
    formats = DeltaOp.attributes.diff(curFormats, prevFormats) || {};
  }
  this.quill.deleteText(range.index-1, 1, Quill.sources.USER);
  if (Object.keys(formats).length > 0) {
    this.quill.formatLine(range.index-1, 1, formats, Quill.sources.USER);
  }
  this.quill.selection.scrollIntoView();
}

function handleDelete(range) {
  if (range.index >= this.quill.getLength() - 1) return;
  this.quill.deleteText(range.index, 1, Quill.sources.USER);
}

function handleDeleteRange(range) {
  this.quill.deleteText(range, Quill.sources.USER);
  this.quill.setSelection(range.index, Quill.sources.SILENT);
  this.quill.selection.scrollIntoView();
}

function handleEnter(range, context) {
  if (range.length > 0) {
    this.quill.scroll.deleteAt(range.index, range.length);  // So we do not trigger text-change
  }
  let lineFormats = Object.keys(context.format).reduce(function(lineFormats, format) {
    if (Parchment.query(format, Parchment.Scope.BLOCK) && !Array.isArray(context.format[format])) {
      lineFormats[format] = context.format[format];
    }
    return lineFormats;
  }, {});
  this.quill.insertText(range.index, '\n', lineFormats, Quill.sources.USER);
  this.quill.selection.scrollIntoView();
  Object.keys(context.format).forEach((name) => {
    if (lineFormats[name] != null) return;
    if (Array.isArray(context.format[name])) return;
    if (name === 'link') return;
    this.quill.format(name, context.format[name], Quill.sources.USER);
  });
}

function makeCodeBlockHandler(indent) {
  return {
    key: Keyboard.keys.TAB,
    shiftKey: !indent,
    format: {'code-block': true },
    handler: function(range) {
      let CodeBlock = Parchment.query('code-block');
      let index = range.index, length = range.length;
      let [block, offset] = this.quill.scroll.descendant(CodeBlock, index);
      if (block == null) return;
      let scrollOffset = this.quill.scroll.offset(block);
      let start = block.newlineIndex(offset, true) + 1;
      let end = block.newlineIndex(scrollOffset + offset + length);
      let lines = block.domNode.textContent.slice(start, end).split('\n');
      offset = 0;
      lines.forEach((line, i) => {
        if (indent) {
          block.insertAt(start + offset, CodeBlock.TAB);
          offset += CodeBlock.TAB.length;
          if (i === 0) {
            index += CodeBlock.TAB.length;
          } else {
            length += CodeBlock.TAB.length;
          }
        } else if (line.startsWith(CodeBlock.TAB)) {
          block.deleteAt(start + offset, CodeBlock.TAB.length);
          offset -= CodeBlock.TAB.length;
          if (i === 0) {
            index -= CodeBlock.TAB.length;
          } else {
            length -= CodeBlock.TAB.length;
          }
        }
        offset += line.length + 1;
      });
      this.quill.update(Quill.sources.USER);
      this.quill.setSelection(index, length, Quill.sources.SILENT);
    }
  };
}

function makeFormatHandler(format) {
  return {
    key: format[0].toUpperCase(),
    shortKey: true,
    handler: function(range, context) {
      this.quill.format(format, !context.format[format], Quill.sources.USER);
    }
  };
}

function normalize(binding) {
  if (typeof binding === 'string' || typeof binding === 'number') {
    return normalize({ key: binding });
  }
  if (typeof binding === 'object') {
    binding = clone(binding, false);
  }
  if (typeof binding.key === 'string') {
    if (Keyboard.keys[binding.key.toUpperCase()] != null) {
      binding.key = Keyboard.keys[binding.key.toUpperCase()];
    } else if (binding.key.length === 1) {
      binding.key = binding.key.toUpperCase().charCodeAt(0);
    } else {
      return null;
    }
  }
  return binding;
}

export default Keyboard;
<commit_msg>no longer necessary given parchment fix<commit_after>import clone from 'clone';
import equal from 'deep-equal';
import extend from 'extend';
import DeltaOp from 'quill-delta/lib/op';
import Parchment from 'parchment';
import Quill from '../core/quill';
import logger from '../core/logger';
import Module from '../core/module';

let debug = logger('quill:keyboard');

const SHORTKEY = /Mac/i.test(navigator.platform) ? 'metaKey' : 'ctrlKey';

class Keyboard extends Module {
  static match(evt, binding) {
    binding = normalize(binding);
    if (!!binding.shortKey !== evt[SHORTKEY] && binding.shortKey !== null) return false;
    if (['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].some(function(key) {
      return (key != SHORTKEY && !!binding[key] !== evt[key] && binding[key] !== null);
    })) {
      return false;
    }
    return binding.key === (evt.which || evt.keyCode);
  }

  constructor(quill, options) {
    super(quill, options);
    this.bindings = {};
    Object.keys(this.options.bindings).forEach((name) => {
      if (this.options.bindings[name]) {
        this.addBinding(this.options.bindings[name]);
      }
    });
    this.addBinding({ key: Keyboard.keys.ENTER, shiftKey: null }, handleEnter);
    this.addBinding({ key: Keyboard.keys.ENTER, metaKey: null, ctrlKey: null, altKey: null }, function() {});
    if (/Gecko/i.test(navigator.userAgent)) {
      // Need to handle delete and backspace for Firefox in the general case #1171
      this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: true }, handleBackspace);
      this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: true }, handleDelete);
    } else {
      this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: true, prefix: /^.?$/ }, handleBackspace);
      this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: true, suffix: /^.?$/ }, handleDelete);
    }
    this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: false }, handleDeleteRange);
    this.addBinding({ key: Keyboard.keys.DELETE }, { collapsed: false }, handleDeleteRange);
    this.listen();
  }

  addBinding(key, context = {}, handler = {}) {
    let binding = normalize(key);
    if (binding == null || binding.key == null) {
      return debug.warn('Attempted to add invalid keyboard binding', binding);
    }
    if (typeof context === 'function') {
      context = { handler: context };
    }
    if (typeof handler === 'function') {
      handler = { handler: handler };
    }
    binding = extend(binding, context, handler);
    this.bindings[binding.key] = this.bindings[binding.key] || [];
    this.bindings[binding.key].push(binding);
  }

  listen() {
    this.quill.root.addEventListener('keydown', (evt) => {
      if (evt.defaultPrevented) return;
      let which = evt.which || evt.keyCode;
      let bindings = (this.bindings[which] || []).filter(function(binding) {
        return Keyboard.match(evt, binding);
      });
      if (bindings.length === 0) return;
      let range = this.quill.getSelection();
      if (range == null || !this.quill.hasFocus()) return;
      let [line, offset] = this.quill.scroll.line(range.index);
      let [leafStart, offsetStart] = this.quill.scroll.leaf(range.index);
      let [leafEnd, offsetEnd] = range.length === 0 ? [leafStart, offsetStart] : this.quill.scroll.leaf(range.index + range.length);
      let prefixText = leafStart instanceof Parchment.Text ? leafStart.value().slice(0, offsetStart) : '';
      let suffixText = leafEnd instanceof Parchment.Text ? leafEnd.value().slice(offsetEnd) : '';
      let curContext = {
        collapsed: range.length === 0,
        empty: range.length === 0 && line.length() <= 1,
        format: this.quill.getFormat(range),
        offset: offset,
        prefix: prefixText,
        suffix: suffixText
      };
      let prevented = bindings.some((binding) => {
        if (binding.collapsed != null && binding.collapsed !== curContext.collapsed) return false;
        if (binding.empty != null && binding.empty !== curContext.empty) return false;
        if (binding.offset != null && binding.offset !== curContext.offset) return false;
        if (Array.isArray(binding.format)) {
          // any format is present
          if (binding.format.every(function(name) {
            return curContext.format[name] == null;
          })) {
            return false;
          }
        } else if (typeof binding.format === 'object') {
          // all formats must match
          if (!Object.keys(binding.format).every(function(name) {
            if (binding.format[name] === true) return curContext.format[name] != null;
            if (binding.format[name] === false) return curContext.format[name] == null;
            return equal(binding.format[name], curContext.format[name]);
          })) {
            return false;
          }
        }
        if (binding.prefix != null && !binding.prefix.test(curContext.prefix)) return false;
        if (binding.suffix != null && !binding.suffix.test(curContext.suffix)) return false;
        return binding.handler.call(this, range, curContext) !== true;
      });
      if (prevented) {
        evt.preventDefault();
      }
    });
  }
}

Keyboard.keys = {
  BACKSPACE: 8,
  TAB: 9,
  ENTER: 13,
  ESCAPE: 27,
  LEFT: 37,
  UP: 38,
  RIGHT: 39,
  DOWN: 40,
  DELETE: 46
};

Keyboard.DEFAULTS = {
  bindings: {
    'bold'      : makeFormatHandler('bold'),
    'italic'    : makeFormatHandler('italic'),
    'underline' : makeFormatHandler('underline'),
    'indent': {
      // highlight tab or tab at beginning of list, indent or blockquote
      key: Keyboard.keys.TAB,
      format: ['blockquote', 'indent', 'list'],
      handler: function(range, context) {
        if (context.collapsed && context.offset !== 0) return true;
        this.quill.format('indent', '+1', Quill.sources.USER);
      }
    },
    'outdent': {
      key: Keyboard.keys.TAB,
      shiftKey: true,
      format: ['blockquote', 'indent', 'list'],
      // highlight tab or tab at beginning of list, indent or blockquote
      handler: function(range, context) {
        if (context.collapsed && context.offset !== 0) return true;
        this.quill.format('indent', '-1', Quill.sources.USER);
      }
    },
    'outdent backspace': {
      key: Keyboard.keys.BACKSPACE,
      collapsed: true,
      format: ['blockquote', 'indent', 'list'],
      offset: 0,
      handler: function(range, context) {
        if (context.format.indent != null) {
          this.quill.format('indent', '-1', Quill.sources.USER);
        } else if (context.format.blockquote != null) {
          this.quill.format('blockquote', false, Quill.sources.USER);
        } else if (context.format.list != null) {
          this.quill.format('list', false, Quill.sources.USER);
        }
      }
    },
    'indent code-block': makeCodeBlockHandler(true),
    'outdent code-block': makeCodeBlockHandler(false),
    'remove tab': {
      key: Keyboard.keys.TAB,
      shiftKey: true,
      collapsed: true,
      prefix: /\t$/,
      handler: function(range) {
        this.quill.deleteText(range.index - 1, 1, Quill.sources.USER);
      }
    },
    'tab': {
      key: Keyboard.keys.TAB,
      handler: function(range, context) {
        if (!context.collapsed) {
          this.quill.scroll.deleteAt(range.index, range.length);
        }
        this.quill.insertText(range.index, '\t', Quill.sources.USER);
        this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
      }
    },
    'list empty enter': {
      key: Keyboard.keys.ENTER,
      collapsed: true,
      format: ['list'],
      empty: true,
      handler: function(range, context) {
        this.quill.format('list', false, Quill.sources.USER);
        if (context.format.indent) {
          this.quill.format('indent', false, Quill.sources.USER);
        }
      }
    },
    'checklist enter': {
      key: Keyboard.keys.ENTER,
      collapsed: true,
      format: { list: 'checked' },
      handler: function(range) {
        this.quill.scroll.insertAt(range.index, '\n');
        let [line, ] = this.quill.scroll.line(range.index + 1);
        line.format('list', 'unchecked');
        this.quill.update(Quill.sources.USER);
        this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
        this.quill.selection.scrollIntoView();
      }
    },
    'header enter': {
      key: Keyboard.keys.ENTER,
      collapsed: true,
      format: ['header'],
      suffix: /^$/,
      handler: function(range) {
        this.quill.scroll.insertAt(range.index, '\n');
        this.quill.formatText(range.index + 1, 1, 'header', false, Quill.sources.USER);
        this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
        this.quill.selection.scrollIntoView();
      }
    },
    'list autofill': {
      key: ' ',
      collapsed: true,
      format: { list: false },
      prefix: /^(1\.|-)$/,
      handler: function(range, context) {
        let length = context.prefix.length;
        this.quill.scroll.deleteAt(range.index - length, length);
        this.quill.formatLine(range.index - length, 1, 'list', length === 1 ? 'bullet' : 'ordered', Quill.sources.USER);
        this.quill.setSelection(range.index - length, Quill.sources.SILENT);
      }
    }
  }
};

function handleBackspace(range, context) {
  if (range.index === 0) return;
  let [line, ] = this.quill.scroll.line(range.index);
  let formats = {};
  if (context.offset === 0) {
    let curFormats = line.formats();
    let prevFormats = this.quill.getFormat(range.index-1, 1);
    formats = DeltaOp.attributes.diff(curFormats, prevFormats) || {};
  }
  this.quill.deleteText(range.index-1, 1, Quill.sources.USER);
  if (Object.keys(formats).length > 0) {
    this.quill.formatLine(range.index-1, 1, formats, Quill.sources.USER);
  }
  this.quill.selection.scrollIntoView();
}

function handleDelete(range) {
  if (range.index >= this.quill.getLength() - 1) return;
  this.quill.deleteText(range.index, 1, Quill.sources.USER);
}

function handleDeleteRange(range) {
  this.quill.deleteText(range, Quill.sources.USER);
  this.quill.setSelection(range.index, Quill.sources.SILENT);
  this.quill.selection.scrollIntoView();
}

function handleEnter(range, context) {
  if (range.length > 0) {
    this.quill.scroll.deleteAt(range.index, range.length);  // So we do not trigger text-change
  }
  let lineFormats = Object.keys(context.format).reduce(function(lineFormats, format) {
    if (Parchment.query(format, Parchment.Scope.BLOCK) && !Array.isArray(context.format[format])) {
      lineFormats[format] = context.format[format];
    }
    return lineFormats;
  }, {});
  this.quill.insertText(range.index, '\n', lineFormats, Quill.sources.USER);
  this.quill.selection.scrollIntoView();
  Object.keys(context.format).forEach((name) => {
    if (lineFormats[name] != null) return;
    if (Array.isArray(context.format[name])) return;
    if (name === 'link') return;
    this.quill.format(name, context.format[name], Quill.sources.USER);
  });
}

function makeCodeBlockHandler(indent) {
  return {
    key: Keyboard.keys.TAB,
    shiftKey: !indent,
    format: {'code-block': true },
    handler: function(range) {
      let CodeBlock = Parchment.query('code-block');
      let index = range.index, length = range.length;
      let [block, offset] = this.quill.scroll.descendant(CodeBlock, index);
      if (block == null) return;
      let scrollOffset = this.quill.scroll.offset(block);
      let start = block.newlineIndex(offset, true) + 1;
      let end = block.newlineIndex(scrollOffset + offset + length);
      let lines = block.domNode.textContent.slice(start, end).split('\n');
      offset = 0;
      lines.forEach((line, i) => {
        if (indent) {
          block.insertAt(start + offset, CodeBlock.TAB);
          offset += CodeBlock.TAB.length;
          if (i === 0) {
            index += CodeBlock.TAB.length;
          } else {
            length += CodeBlock.TAB.length;
          }
        } else if (line.startsWith(CodeBlock.TAB)) {
          block.deleteAt(start + offset, CodeBlock.TAB.length);
          offset -= CodeBlock.TAB.length;
          if (i === 0) {
            index -= CodeBlock.TAB.length;
          } else {
            length -= CodeBlock.TAB.length;
          }
        }
        offset += line.length + 1;
      });
      this.quill.update(Quill.sources.USER);
      this.quill.setSelection(index, length, Quill.sources.SILENT);
    }
  };
}

function makeFormatHandler(format) {
  return {
    key: format[0].toUpperCase(),
    shortKey: true,
    handler: function(range, context) {
      this.quill.format(format, !context.format[format], Quill.sources.USER);
    }
  };
}

function normalize(binding) {
  if (typeof binding === 'string' || typeof binding === 'number') {
    return normalize({ key: binding });
  }
  if (typeof binding === 'object') {
    binding = clone(binding, false);
  }
  if (typeof binding.key === 'string') {
    if (Keyboard.keys[binding.key.toUpperCase()] != null) {
      binding.key = Keyboard.keys[binding.key.toUpperCase()];
    } else if (binding.key.length === 1) {
      binding.key = binding.key.toUpperCase().charCodeAt(0);
    } else {
      return null;
    }
  }
  return binding;
}

export default Keyboard;
<|endoftext|>


<commit_before><commit_msg>energy goes to 5 decimal places<commit_after><|endoftext|>


<commit_before>var Pusher = function(application_key, channel) {
  this.url = 'ws://' + Pusher.host + '/app/' + application_key;
  this.key = application_key;
  this.socket_id;
  this.callbacks = {};
  this.global_callbacks = [];
  this.channels = new Pusher.Channels(channel);
  this.connected = false;
  this.connect();

  var self = this;

  this.bind('connection_established', function(data) {
    self.connected = true;
    self.socket_id = data.socket_id;
    self.subscribeAll(self.channels.channels);
  });

  this.bind('pusher:error', function(data) {
    Pusher.log("Pusher : error : " + data.message);
  });
};

Pusher.prototype = {
  connect: function() {
    var self = this;

    if (window["WebSocket"]) {
      this.connection = new WebSocket(this.url);
      this.connection.onmessage = function() {
        self.onmessage.apply(self, arguments);
      };
      this.connection.onclose = function() {
        self.onclose.apply(self, arguments);
      };
      this.connection.onopen = function() {
        self.onopen.apply(self, arguments);
      };
    } else {
      // Mock connection object if WebSockets are not available.
      this.connection = {};
    }
  },

  bind: function(event_name, callback) {
    this.callbacks[event_name] = this.callbacks[event_name] || [];
    this.callbacks[event_name].push(callback);
    return this;
  },

  bind_all: function(callback) {
    this.global_callbacks.push(callback);
    return this;
  },

  // Not currently supported by pusherapp.com
  trigger: function(event_name, data) {
    var payload = JSON.stringify({ 'event' : event_name, 'data' : data });
    Pusher.log("Pusher : sending event : " + payload);
    this.connection.send(payload);
    return this;
  },

  subscribeAll: function(channel_names) {
    for (var i = 0; i < channel_names.length; i++) {
      this.subscribe(channel_names[i]);
    }
  },

  subscribe: function(channel_name) {
    this.channels.add(channel_name);

    if (this.connected) {
      if (channel_name.indexOf("private-") === 0) {
        var self = this;
        var xhr = window.XMLHttpRequest ?
          new XMLHttpRequest() :
          new ActiveXObject("Microsoft.XMLHTTP");
        xhr.open("POST", Pusher.channel_auth_endpoint, true);
        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
        xhr.onreadystatechange = function() {
          if (xhr.readyState == 4) {
            if (xhr.status == 200) {
              var data = JSON.parse(xhr.responseText);
              self.trigger('pusher:subscribe', {
                channel: channel_name,
                auth: self.key + ':' + data.auth
              });
            } else {
              Pusher.log("Couldn't get auth info from your webapp" + status);
            }
          }
        };
        xhr.send('socket_id=' + encodeURIComponent(this.socket_id) + '&channel_name=' + encodeURIComponent(channel_name));
      } else {
        this.trigger('pusher:subscribe', {
          channel: channel_name
        });
      }
    }
  },

  unsubscribe: function(channel_name) {
    this.channels.remove(channel_name);

    if (this.connected) {
      this.trigger('pusher:unsubscribe', {
        channel: channel_name
      });
    }
  },

  onmessage: function(evt) {
    var params = JSON.parse(evt.data);
    if (params.socket_id && params.socket_id == this.socket_id) return;
    var event_name = params.event;
    var event_data = Pusher.parser(params.data);
    Pusher.log("Pusher : event received : " + event_name +" : ", event_data);
    this.dispatch_global_callbacks(event_name, event_data);
    this.dispatch(event_name, event_data);
  },

  onclose: function() {
    this.dispatch('close', null);
    this.connected = false;

    var self = this;

    if (Pusher.allow_reconnect){
      Pusher.log('Pusher : socket closed : Reconnecting in 5 seconds...');

      setTimeout(function() {
        Pusher.log('Pusher : reconnecting');
        self.connect();
      }, 5000);
    }
  },

  onopen: function() {
    this.dispatch('open', null);
  },

  dispatch: function(event_name, event_data) {
    var callbacks = this.callbacks[event_name];

    if (callbacks) {
      for (var i = 0; i < callbacks.length; i++) {
        callbacks[i](event_data);
      }
    } else {
      Pusher.log('Pusher : No callbacks for ' + event_name);
    }
  },

  dispatch_global_callbacks: function(event_name, event_data) {
    for (var i = 0; i < this.global_callbacks.length; i++) {
      this.global_callbacks[i](event_name, event_data);
    }
  }
};

// Pusher defaults

Pusher.VERSION = "<%= VERSION %>";
Pusher.host = "ws.pusherapp.com:80";
Pusher.channel_auth_endpoint = '/pusher/auth';
Pusher.log = function(msg){}; // e.g. function(m){console.log(m)}
Pusher.allow_reconnect = true;
Pusher.parser = function(data) {
  try {
    return JSON.parse(data);
  } catch(e) {
    Pusher.log("Pusher : data attribute not valid JSON - you may wish to implement your own Pusher.parser");
    return data;
  }
};
<commit_msg>Bugfix - key should be returned as part of auth<commit_after>var Pusher = function(application_key, channel) {
  this.url = 'ws://' + Pusher.host + '/app/' + application_key;
  this.key = application_key;
  this.socket_id;
  this.callbacks = {};
  this.global_callbacks = [];
  this.channels = new Pusher.Channels(channel);
  this.connected = false;
  this.connect();

  var self = this;

  this.bind('connection_established', function(data) {
    self.connected = true;
    self.socket_id = data.socket_id;
    self.subscribeAll(self.channels.channels);
  });

  this.bind('pusher:error', function(data) {
    Pusher.log("Pusher : error : " + data.message);
  });
};

Pusher.prototype = {
  connect: function() {
    var self = this;

    if (window["WebSocket"]) {
      this.connection = new WebSocket(this.url);
      this.connection.onmessage = function() {
        self.onmessage.apply(self, arguments);
      };
      this.connection.onclose = function() {
        self.onclose.apply(self, arguments);
      };
      this.connection.onopen = function() {
        self.onopen.apply(self, arguments);
      };
    } else {
      // Mock connection object if WebSockets are not available.
      this.connection = {};
    }
  },

  bind: function(event_name, callback) {
    this.callbacks[event_name] = this.callbacks[event_name] || [];
    this.callbacks[event_name].push(callback);
    return this;
  },

  bind_all: function(callback) {
    this.global_callbacks.push(callback);
    return this;
  },

  // Not currently supported by pusherapp.com
  trigger: function(event_name, data) {
    var payload = JSON.stringify({ 'event' : event_name, 'data' : data });
    Pusher.log("Pusher : sending event : " + payload);
    this.connection.send(payload);
    return this;
  },

  subscribeAll: function(channel_names) {
    for (var i = 0; i < channel_names.length; i++) {
      this.subscribe(channel_names[i]);
    }
  },

  subscribe: function(channel_name) {
    this.channels.add(channel_name);

    if (this.connected) {
      if (channel_name.indexOf("private-") === 0) {
        var self = this;
        var xhr = window.XMLHttpRequest ?
          new XMLHttpRequest() :
          new ActiveXObject("Microsoft.XMLHTTP");
        xhr.open("POST", Pusher.channel_auth_endpoint, true);
        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
        xhr.onreadystatechange = function() {
          if (xhr.readyState == 4) {
            if (xhr.status == 200) {
              var data = JSON.parse(xhr.responseText);
              self.trigger('pusher:subscribe', {
                channel: channel_name,
                auth: data.auth
              });
            } else {
              Pusher.log("Couldn't get auth info from your webapp" + status);
            }
          }
        };
        xhr.send('socket_id=' + encodeURIComponent(this.socket_id) + '&channel_name=' + encodeURIComponent(channel_name));
      } else {
        this.trigger('pusher:subscribe', {
          channel: channel_name
        });
      }
    }
  },

  unsubscribe: function(channel_name) {
    this.channels.remove(channel_name);

    if (this.connected) {
      this.trigger('pusher:unsubscribe', {
        channel: channel_name
      });
    }
  },

  onmessage: function(evt) {
    var params = JSON.parse(evt.data);
    if (params.socket_id && params.socket_id == this.socket_id) return;
    var event_name = params.event;
    var event_data = Pusher.parser(params.data);
    Pusher.log("Pusher : event received : " + event_name +" : ", event_data);
    this.dispatch_global_callbacks(event_name, event_data);
    this.dispatch(event_name, event_data);
  },

  onclose: function() {
    this.dispatch('close', null);
    this.connected = false;

    var self = this;

    if (Pusher.allow_reconnect){
      Pusher.log('Pusher : socket closed : Reconnecting in 5 seconds...');

      setTimeout(function() {
        Pusher.log('Pusher : reconnecting');
        self.connect();
      }, 5000);
    }
  },

  onopen: function() {
    this.dispatch('open', null);
  },

  dispatch: function(event_name, event_data) {
    var callbacks = this.callbacks[event_name];

    if (callbacks) {
      for (var i = 0; i < callbacks.length; i++) {
        callbacks[i](event_data);
      }
    } else {
      Pusher.log('Pusher : No callbacks for ' + event_name);
    }
  },

  dispatch_global_callbacks: function(event_name, event_data) {
    for (var i = 0; i < this.global_callbacks.length; i++) {
      this.global_callbacks[i](event_name, event_data);
    }
  }
};

// Pusher defaults

Pusher.VERSION = "<%= VERSION %>";
Pusher.host = "ws.pusherapp.com:80";
Pusher.channel_auth_endpoint = '/pusher/auth';
Pusher.log = function(msg){}; // e.g. function(m){console.log(m)}
Pusher.allow_reconnect = true;
Pusher.parser = function(data) {
  try {
    return JSON.parse(data);
  } catch(e) {
    Pusher.log("Pusher : data attribute not valid JSON - you may wish to implement your own Pusher.parser");
    return data;
  }
};
<|endoftext|>


<commit_before>/**
 * @fileoverview This module provides support for loading CommonJS and Narwhal packages.
 */

var system = require('system');
var engine = require('ringo/engine');

export('load', 'loadPackages', 'loadPackage', 'normalize', 'catalog');

/**
 * @ignore
 */
var catalog;

/**
 * Load packages from the default `packages` directory.
 */
function load() {
    // Check if we're either already set up or running in secure sandbox mode.
    if (catalog || !require.paths) {
        return;
    }
    catalog = {};
    loadPackages(engine.getPackageRepository());
}

/**
 * Load all packages contained in `repository`.
 * @param repository String|Repository a path or Ringo repository
 */
function loadPackages(repository) {
    if (typeof repository === "string") {
        repository = getRepository(repository);
    }
    if (repository && repository.exists()) {
        repository.getRepositories().forEach(loadPackage);
    }
}

/**
 * Load a single package located in `repository`.
 * @param repository String|Repository a path or Ringo repository
 */
function loadPackage(repository) {
    if (typeof repository === "string") {
        repository = getRepository(repository);
    }
    try {
        var jsonPath = repository.getResource("package.json");
        var pkg = JSON.parse(jsonPath.getContent());
        var name = pkg.name || repository.getName();
        if (catalog[name]) {
            system.stderr.print(
                    "Tried to load package `" + name + "` " +
                    "from `" + repository + "`, but a package with the same " +
                    "name has already been loaded " +
                    "from `" + catalog[name].directory + "`.");
            return;
        }

        pkg.directory = repository;

        // check engine specific libs
        handleResource(pkg.engines || "engines", function(engines) {
            var dir = repository.getChildRepository("engines");
            for each (var engine in system.engines) {
                var path = dir.getChildRepository(engine + "/lib");
                if (path.exists()) {
                    require.paths.push(path);
                }
            }
        });

        var lib = (pkg.directories && pkg.directories.lib) || pkg.lib || "lib";
        handleResource(lib, function(lib) {
            require.paths.push(repository.getChildRepository(lib));
        });

        var jars = (pkg.directories && pkg.directories.jars) || pkg.jars;
        handleResource(jars, function(res) {
            var files;
            var path = repository.getChildRepository(res);
            if (path.exists()) {
                files = [p for each (p in path.getResources())
                           if (p.getName().match(/\.jar$/))];
            } else {
                files = [repository.getResource(res)];
            }
            for each (var file in files) {
                addToClasspath(file);
            }
        });

        catalog[name] = pkg;

    } catch (error) {
        system.stderr.print(
                "Error configuring package " + repository + ": " + error);
    }
}

/**
 * @ignore
 */
function normalize(catalog) {
    // TODO
}

// helper function to set up package resources
function handleResource(res, callback) {
    if (Array.isArray(res)) {
        res.forEach(callback);
    } else if (res) {
        callback(res);
    }
}
<commit_msg>Added a pure JS version of the code to discover Common-JS packages on the classpath.<commit_after>/**
 * @fileoverview This module provides support for loading CommonJS and Narwhal packages.
 */

var system = require('system');
var engine = require('ringo/engine');
var log = require('ringo/logging').getLogger(module.id);

export('load', 'loadPackages', 'loadPackage', 'normalize', 'catalog');

/**
 * @ignore
 */
var catalog;

/**
 * Load packages from the default `packages` directory.
 */
function load() {
    // Check if we're either already set up or running in secure sandbox mode.
    if (catalog || !require.paths) {
        return;
    }
    catalog = {};
    loadPackages(engine.getPackageRepository());
	discoverClasspathModules();
}

/**
 * Load all packages contained in `repository`.
 * @param repository String|Repository a path or Ringo repository
 */
function loadPackages(repository) {
    if (typeof repository === "string") {
        repository = getRepository(repository);
    }
    if (repository && repository.exists()) {
        repository.getRepositories().forEach(loadPackage);
    }
}

/**
 * Load a single package located in `repository`.
 * @param repository String|Repository a path or Ringo repository
 */
function loadPackage(repository) {
    if (typeof repository === "string") {
        repository = getRepository(repository);
    }
    try {
        var jsonPath = repository.getResource("package.json");
        var pkg = JSON.parse(jsonPath.getContent());
        var name = pkg.name || repository.getName();
        if (catalog[name]) {
            system.stderr.print(
                    "Tried to load package `" + name + "` " +
                    "from `" + repository + "`, but a package with the same " +
                    "name has already been loaded " +
                    "from `" + catalog[name].directory + "`.");
            return;
        }

        pkg.directory = repository;

        // check engine specific libs
        handleResource(pkg.engines || "engines", function(engines) {
            var dir = repository.getChildRepository("engines");
            for each (var engine in system.engines) {
                var path = dir.getChildRepository(engine + "/lib");
                if (path.exists()) {
                    require.paths.push(path);
                }
            }
        });

        var lib = (pkg.directories && pkg.directories.lib) || pkg.lib || "lib";
        handleResource(lib, function(lib) {
            require.paths.push(repository.getChildRepository(lib));
        });

        var jars = (pkg.directories && pkg.directories.jars) || pkg.jars;
        handleResource(jars, function(res) {
            var files;
            var path = repository.getChildRepository(res);
            if (path.exists()) {
                files = [p for each (p in path.getResources())
                           if (p.getName().match(/\.jar$/))];
            } else {
                files = [repository.getResource(res)];
            }
            for each (var file in files) {
                addToClasspath(file);
            }
        });

        catalog[name] = pkg;

    } catch (error) {
        system.stderr.print(
                "Error configuring package " + repository + ": " + error);
    }
}

/**
 * @ignore
 */
function normalize(catalog) {
    // TODO
}

// helper function to set up package resources
function handleResource(res, callback) {
    if (Array.isArray(res)) {
        res.forEach(callback);
    } else if (res) {
        callback(res);
    }
}

/**
 * Scour the current classpath looking for Common-JS packages to be added to the repository
 * list for package resolution. A Common-JS package is identified as an archive on the Java
 * classpath containing a package.json resource in the root folder.
 * (@see http://wiki.commonjs.org/wiki/Packages/1.0)
 */
function discoverClasspathModules() {

	var loader = java.lang.Thread.currentThread().getContextClassLoader();

	log.debug('Seeking for Common-JS packages on the classpath');

	var resources = loader.getResources('package.json');

	while (resources.hasMoreElements()) {
		var url = resources.nextElement();
		var path = url.getPath();

		// If the package.json file is found in a jar/zip file, we need to strip the url
		// down to resolve the root.
		if (/^file:/.test(path)) path = path.substring(5);
		var paths = path.split('!');

		log.info('Found package.json at ' + paths[0] + ' in url: ' + url.getPath());

		loadPackage(new org.ringojs.repository.ZipRepository(paths[0]));
	}
}
<|endoftext|>


<commit_before>/*  Copyright (C) 2016 Milan Pässler
    Copyright (C) 2016 HopGlass Server contributors

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>. */

'use strict'

var fs = require('fs')
var _ = require('lodash')
var async = require('async')

var config = {
  /* eslint-disable quotes */
  receivers: [
    { module: "announced" },
    { module: "aliases",
      overlay: true
    }
  ],
  ifaces: [
    "bat0"
  ],
  storage: {
    interval: 300,
    file: "./raw.json"
  },
  purge: {
    maxAge: 14,      // in days
    interval: 86400  //  1 day
  }
}

module.exports = function (configData) {
  if (configData.ifaces)
    delete config.ifaces

  if (configData.receivers)
    delete config.receivers

  _.merge(config, configData)

  var receiverList = []
  var raw = {}
  var overlay = {}

  try {
    raw = JSON.parse(fs.readFileSync(config.storage.file, 'utf8'))
  } catch(err) {
    console.log(err)
  }

  var api = {}
  api.receiverCallback  = receiverCallback
  api.sharedConfig = config
  api.getRaw = getRaw
  for (let i in config.receivers) {
    var r = config.receivers[i]
    receiverList.push(require(__dirname + '/receiver/' + r.module)(i, r.config, api))
  }

  function receiverCallback(id, obj, receiverId) {
    var receiverConf = config.receivers[receiverId]
    if (receiverConf.overlay) {
      _.merge(overlay[id], obj)
      return
    } else
      _.merge(raw[id], obj)

    if (!raw[id]) {
      raw[id] = {}
      raw[id].firstseen = new Date().toISOString()
    }

    raw[id].lastseen = new Date().toISOString()

    if (!raw[id].lastupdate)
      raw[id].lastupdate = {}
    if (obj.nodeinfo)
      raw[id].lastupdate.nodeinfo = new Date().toISOString()
    if (obj.statistics)
      raw[id].lastupdate.statistics = new Date().toISOString()
    if (obj.neighbours)
      raw[id].lastupdate.neighbours = new Date().toISOString()
  }

  function getRaw() {
    return _.merge({}, raw)
  }

  function getData(query) {
    var data = getRaw()
    _.merge(data, overlay)

    if (typeof query === 'object')
      data = filterData(data, query)

    return data
  }

  function filterData(data, query) {
    // filtern anhand der übergebenen Filterwerte
    switch (query.filter) {
    case 'site':
      return _.filter(data, function(o) {
        return _.get(o, 'nodeinfo.system.site_code', 'unknown') === query.value ? true : false
      })
    case 'firmware_release':
      return _.filter(data, function(o) {
        return _.get(o, 'nodeinfo.software.firmware.release', 'unknown') === query.value ? true : false
      })
    case 'firstseen':
      return _.filter(data, function(o) {
        var firstseen = (new Date(o.firstseen)).getTime()
        var now = (new Date()).getTime()
        var v = parseInt(query.value)*1000
        if (v >= 0) {
          return now - firstseen <= v ? true : false           // all nodes seen last n seconds
        } else {
          return now - firstseen > Math.abs(v) ? true : false  // all nodes not seen in last n seconds
        }
      })
    case 'lastseen':
      return _.filter(data, function(o) {
        var lastseen = (new Date(o.lastseen)).getTime()
        var now = (new Date()).getTime()
        var v = parseInt(query.value)*1000
        if (v >= 0) {
          return now - lastseen <= v ? true : false
        } else {
          return now - lastseen > Math.abs(v) ? true : false
        }
      })
    case 'uptime':
      return _.filter(data, function(o) {
        var uptime = parseInt(_.get(o, 'statistics.uptime', '-1'))
        var v = parseInt(query.value)
        if (v >= 0) {
          return uptime <= v ? true : false
        } else {
          return uptime > Math.abs(v) ? true : false
        }
      })
    case 'clients':
      return _.filter(data, function(o) {
        var clients = parseInt(_.get(o, 'statistics.clients.total', '-1'))
        var v = parseInt(query.value)
        if (v >= 0) {
          return clients >= v ? true : false
        } else {
          return clients < Math.abs(v) ? true : false
        }
      })
    default:
      return data
    }
  }

  function purgeData() {
    var now = new Date().getTime()
    async.forEachOf(raw, function(n, k, finished) {
      var lastseen = (new Date(n.lastseen)).getTime()
      if (now - lastseen >= config.purge.maxAge*86400*1000 || typeof n.lastseen === 'undefined') {
        console.info('purge old node ' + k)
        delete raw[k]
      }
      finished()
    })
  }
  purgeData()
  setInterval(purgeData, config.purge.interval*1000)

  function storeData() {
    try {
      var fn = fs.openSync(config.storage.file + '.tmp', 'w')
      fs.writeSync(fn, JSON.stringify(getRaw()))
      fs.fsyncSync(fn) // take care that it was actually written to disk
      fs.closeSync(fn)
      fs.renameSync(config.storage.file + '.tmp', config.storage.file) // prevent overwriting with an unfinished backup (happens if disk is full)
    } catch(err) {
      console.error(err)
    }
  }
  setInterval(storeData, config.storage.interval*1000)

  process.on('SIGINT', function () {
    storeData()
    process.exit(2)
  })

  process.on('SIGTERM', function () { // systemd kills with SIGTERM
    storeData()
    process.exit(0)
  })

  var exports = {}
  exports.getData = getData
  exports.getRaw  = getRaw
  return exports
}
<commit_msg>fix receiver<commit_after>/*  Copyright (C) 2016 Milan Pässler
    Copyright (C) 2016 HopGlass Server contributors

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>. */

'use strict'

var fs = require('fs')
var _ = require('lodash')
var async = require('async')

var config = {
  /* eslint-disable quotes */
  receivers: [
    { module: "announced" },
    { module: "aliases",
      overlay: true
    }
  ],
  ifaces: [
    "bat0"
  ],
  storage: {
    interval: 300,
    file: "./raw.json"
  },
  purge: {
    maxAge: 14,      // in days
    interval: 86400  //  1 day
  }
}

module.exports = function (configData) {
  if (configData.ifaces)
    delete config.ifaces

  if (configData.receivers)
    delete config.receivers

  _.merge(config, configData)

  var receiverList = []
  var raw = {}
  var overlay = {}

  try {
    raw = JSON.parse(fs.readFileSync(config.storage.file, 'utf8'))
  } catch(err) {
    console.log(err)
  }

  var api = {}
  api.receiverCallback  = receiverCallback
  api.sharedConfig = config
  api.getRaw = getRaw
  for (let i in config.receivers) {
    var r = config.receivers[i]
    receiverList.push(require(__dirname + '/receiver/' + r.module)(i, r.config, api))
  }

  function receiverCallback(id, obj, receiverId) {
    var receiverConf = config.receivers[receiverId]

    if (!raw[id]) {
      raw[id] = {}
      raw[id].firstseen = new Date().toISOString()
    }

    if (receiverConf.overlay) {
      _.merge(overlay[id], obj)
      delete raw[id].lastseen
      delete raw[id].lastupdate
      return
    }

    raw[id].lastseen = new Date().toISOString()

    if (!raw[id].lastupdate)
      raw[id].lastupdate = {}

    if (obj.nodeinfo) {
      raw[id].nodeinfo = obj.nodeinfo
      raw[id].lastupdate.nodeinfo = new Date().toISOString()
    }
    if (obj.statistics) {
      raw[id].statistics = obj.statistics
      raw[id].lastupdate.statistics = new Date().toISOString()
    }
    if (obj.neighbours) {
      raw[id].neighbours = obj.neighbours
      raw[id].lastupdate.neighbours = new Date().toISOString()
    }
  }

  function getRaw() {
    return _.merge({}, raw)
  }

  function getData(query) {
    var data = getRaw()
    _.merge(data, overlay)

    if (typeof query === 'object')
      data = filterData(data, query)

    return data
  }

  function filterData(data, query) {
    // filtern anhand der übergebenen Filterwerte
    switch (query.filter) {
    case 'site':
      return _.filter(data, function(o) {
        return _.get(o, 'nodeinfo.system.site_code', 'unknown') === query.value ? true : false
      })
    case 'firmware_release':
      return _.filter(data, function(o) {
        return _.get(o, 'nodeinfo.software.firmware.release', 'unknown') === query.value ? true : false
      })
    case 'firstseen':
      return _.filter(data, function(o) {
        var firstseen = (new Date(o.firstseen)).getTime()
        var now = (new Date()).getTime()
        var v = parseInt(query.value)*1000
        if (v >= 0) {
          return now - firstseen <= v ? true : false           // all nodes seen last n seconds
        } else {
          return now - firstseen > Math.abs(v) ? true : false  // all nodes not seen in last n seconds
        }
      })
    case 'lastseen':
      return _.filter(data, function(o) {
        var lastseen = (new Date(o.lastseen)).getTime()
        var now = (new Date()).getTime()
        var v = parseInt(query.value)*1000
        if (v >= 0) {
          return now - lastseen <= v ? true : false
        } else {
          return now - lastseen > Math.abs(v) ? true : false
        }
      })
    case 'uptime':
      return _.filter(data, function(o) {
        var uptime = parseInt(_.get(o, 'statistics.uptime', '-1'))
        var v = parseInt(query.value)
        if (v >= 0) {
          return uptime <= v ? true : false
        } else {
          return uptime > Math.abs(v) ? true : false
        }
      })
    case 'clients':
      return _.filter(data, function(o) {
        var clients = parseInt(_.get(o, 'statistics.clients.total', '-1'))
        var v = parseInt(query.value)
        if (v >= 0) {
          return clients >= v ? true : false
        } else {
          return clients < Math.abs(v) ? true : false
        }
      })
    default:
      return data
    }
  }

  function purgeData() {
    var now = new Date().getTime()
    async.forEachOf(raw, function(n, k, finished) {
      var lastseen = (new Date(n.lastseen)).getTime()
      if (now - lastseen >= config.purge.maxAge*86400*1000 || typeof n.lastseen === 'undefined') {
        console.info('purge old node ' + k)
        delete raw[k]
      }
      finished()
    })
  }
  purgeData()
  setInterval(purgeData, config.purge.interval*1000)

  function storeData() {
    try {
      var fn = fs.openSync(config.storage.file + '.tmp', 'w')
      fs.writeSync(fn, JSON.stringify(getRaw()))
      fs.fsyncSync(fn) // take care that it was actually written to disk
      fs.closeSync(fn)
      fs.renameSync(config.storage.file + '.tmp', config.storage.file) // prevent overwriting with an unfinished backup (happens if disk is full)
    } catch(err) {
      console.error(err)
    }
  }
  setInterval(storeData, config.storage.interval*1000)

  process.on('SIGINT', function () {
    storeData()
    process.exit(2)
  })

  process.on('SIGTERM', function () { // systemd kills with SIGTERM
    storeData()
    process.exit(0)
  })

  var exports = {}
  exports.getData = getData
  exports.getRaw  = getRaw
  return exports
}
<|endoftext|>


<commit_before>const sowpods = require('pf-sowpods')

let wordHistory = []
const bag = {
  'a': { count: 9, score: 1 },
  'b': { count: 2, score: 3 },
  'c': { count: 2, score: 3 },
  'd': { count: 4, score: 2 },
  'e': { count: 12, score: 1 },
  'f': { count: 2, score: 4 },
  'g': { count: 3, score: 2 },
  'h': { count: 2, score: 4 },
  'i': { count: 9, score: 1 },
  'j': { count: 1, score: 8 },
  'k': { count: 1, score: 5 },
  'l': { count: 4, score: 1 },
  'm': { count: 2, score: 3 },
  'n': { count: 6, score: 1 },
  'o': { count: 8, score: 1 },
  'p': { count: 2, score: 3 },
  'q': { count: 1, score: 10 },
  'r': { count: 6, score: 1 },
  's': { count: 4, score: 1 },
  't': { count: 6, score: 1 },
  'u': { count: 4, score: 1 },
  'v': { count: 2, score: 4 },
  'w': { count: 2, score: 4 },
  'x': { count: 1, score: 8 },
  'y': { count: 2, score: 4 },
  'z': { count: 1, score: 10 }
}
function scoreLetter (letter, str) {
  try {
    const occurrences = str.toLowerCase().split(letter).length - 1
    const usedBlanks = Math.max(occurrences - bag[letter]['count'], 0)
    return {
      score: bag[letter]['score'] * (occurrences - usedBlanks),
      usedBlanks
    }
  } catch (e) {
    if (e instanceof TypeError) {
      return {
        score: 0,
        usedBlanks: 0
      }
    }
  }
}
function computeWord (str) {
  let score = scrabbleScore(str)
  let isImpossible = str.length > 15 || str.length < 2 || /[^a-zA-Z]/.test(str) || score === null
  let isAllowable = sowpods.verify(str)

  return {
    word: str,
    formatted: scrabbleNotate(str) + (isAllowable ? '' : '*'),
    isValid: !isImpossible && isAllowable,
    isPossible: !isImpossible,
    isAllowable,
    score
  }
}
function scrabbleNotate (str) {
  let word = str.toLowerCase()
  for (const letter of [...new Set(word)]) {
    for (let i = 0; i < bag[letter]['count']; i++) {
      word = word.replace(letter, letter.toUpperCase())
    }
  }
  return word
}
function getPunctuation (bot, score) {
  const minScore = bot.config.get('scrabble.minScore')
  const exclThreshold = bot.config.get('scrabble.exclamationThreshold')
  const numberOfMarks = Math.max(Math.floor((score - minScore) / exclThreshold), 0)
  return '!'.repeat(numberOfMarks) || '.'
}
function scrabbleScore (str) {
  const result = [...new Set(str.toLowerCase())]
    .map(c => scoreLetter(c, str))
    .reduce((a, b) => ({
      'score': a['score'] + b['score'],
      'usedBlanks': a['usedBlanks'] + b['usedBlanks']
    }))
  return result['usedBlanks'] > 2 ? null : result['score']
}
function reportScore (bot, word) {
  if (typeof word === 'string') {
    word = computeWord(word)
  }
  if (!word.isPossible) return 'Not a valid word'
  return `${word.formatted} scores ${word.score} points${getPunctuation(bot, word.score)}`
}
module.exports = {
  commands: {
    scrabble: {
      help: 'Scores a word in Scrabble',
      usage: [ 'word' ],
      command: function (bot, msg) {
        return reportScore(bot, msg.args.word)
      }
    },
    words: {
      help: 'List the recorded high-scoring Scrabble words',
      command: () => wordHistory.map(scrabbleNotate).join(' ')
    }
  },
  events: {
    message: function (bot, nick, to, text) {
      let phrase = text.toUpperCase().split(/[^A-Z]/)
      if (text.match(bot.config.get('irc.controlChar') + 'scrabble')) return
      let words = []
      phrase.forEach((word) => {
        if (wordHistory.includes(word)) return
        words.push(computeWord(word))
      })
      const bestWord = words.reduce((a, b) => (a.score > b.score) ? a : b)
      if (bestWord.score >= bot.config.get('scrabble.minScore')) {
        wordHistory.push(bestWord.word)
        bot.shout(to, nick + ': ' + reportScore(bot, bestWord))
      }
    }
  }
}
<commit_msg>Remove URLs before processing Scrabble scores (#270)<commit_after>const sowpods = require('pf-sowpods')

let wordHistory = []
const bag = {
  'a': { count: 9, score: 1 },
  'b': { count: 2, score: 3 },
  'c': { count: 2, score: 3 },
  'd': { count: 4, score: 2 },
  'e': { count: 12, score: 1 },
  'f': { count: 2, score: 4 },
  'g': { count: 3, score: 2 },
  'h': { count: 2, score: 4 },
  'i': { count: 9, score: 1 },
  'j': { count: 1, score: 8 },
  'k': { count: 1, score: 5 },
  'l': { count: 4, score: 1 },
  'm': { count: 2, score: 3 },
  'n': { count: 6, score: 1 },
  'o': { count: 8, score: 1 },
  'p': { count: 2, score: 3 },
  'q': { count: 1, score: 10 },
  'r': { count: 6, score: 1 },
  's': { count: 4, score: 1 },
  't': { count: 6, score: 1 },
  'u': { count: 4, score: 1 },
  'v': { count: 2, score: 4 },
  'w': { count: 2, score: 4 },
  'x': { count: 1, score: 8 },
  'y': { count: 2, score: 4 },
  'z': { count: 1, score: 10 }
}
function scoreLetter (letter, str) {
  try {
    const occurrences = str.toLowerCase().split(letter).length - 1
    const usedBlanks = Math.max(occurrences - bag[letter]['count'], 0)
    return {
      score: bag[letter]['score'] * (occurrences - usedBlanks),
      usedBlanks
    }
  } catch (e) {
    if (e instanceof TypeError) {
      return {
        score: 0,
        usedBlanks: 0
      }
    }
  }
}
function computeWord (str) {
  let score = scrabbleScore(str)
  let isImpossible = str.length > 15 || str.length < 2 || /[^a-zA-Z]/.test(str) || score === null
  let isAllowable = sowpods.verify(str)

  return {
    word: str,
    formatted: scrabbleNotate(str) + (isAllowable ? '' : '*'),
    isValid: !isImpossible && isAllowable,
    isPossible: !isImpossible,
    isAllowable,
    score
  }
}
function scrabbleNotate (str) {
  let word = str.toLowerCase()
  for (const letter of [...new Set(word)]) {
    for (let i = 0; i < bag[letter]['count']; i++) {
      word = word.replace(letter, letter.toUpperCase())
    }
  }
  return word
}
function getPunctuation (bot, score) {
  const minScore = bot.config.get('scrabble.minScore')
  const exclThreshold = bot.config.get('scrabble.exclamationThreshold')
  const numberOfMarks = Math.max(Math.floor((score - minScore) / exclThreshold), 0)
  return '!'.repeat(numberOfMarks) || '.'
}
function scrabbleScore (str) {
  const result = [...new Set(str.toLowerCase())]
    .map(c => scoreLetter(c, str))
    .reduce((a, b) => ({
      'score': a['score'] + b['score'],
      'usedBlanks': a['usedBlanks'] + b['usedBlanks']
    }))
  return result['usedBlanks'] > 2 ? null : result['score']
}
function reportScore (bot, word) {
  if (typeof word === 'string') {
    word = computeWord(word)
  }
  if (!word.isPossible) return 'Not a valid word'
  return `${word.formatted} scores ${word.score} points${getPunctuation(bot, word.score)}`
}
module.exports = {
  commands: {
    scrabble: {
      help: 'Scores a word in Scrabble',
      usage: [ 'word' ],
      command: function (bot, msg) {
        return reportScore(bot, msg.args.word)
      }
    },
    words: {
      help: 'List the recorded high-scoring Scrabble words',
      command: () => wordHistory.map(scrabbleNotate).join(' ')
    }
  },
  events: {
    message: function (bot, nick, to, text) {
      if (text.match(bot.config.get('irc.controlChar') + 'scrabble')) return
      let phrase = text.toUpperCase().split(/[ '"]/)
      let words = []
      phrase.forEach((word) => {
        word = word.replace(/[^A-Z]+$/, '')
        if (wordHistory.includes(word) || word === '') return
        words.push(computeWord(word))
      })
      const bestWord = words.reduce((a, b) => (a.score > b.score) ? a : b)
      if (bestWord.score >= bot.config.get('scrabble.minScore')) {
        wordHistory.push(bestWord.word)
        bot.shout(to, nick + ': ' + reportScore(bot, bestWord))
      }
    }
  }
}
<|endoftext|>


<commit_before>/**
 *    Returns a promise that fetches the token and resolves with it if it's found
 *    @dep    {node}     fs
 *    @dep    {npm}      inquirer
 *    @param  {Function} callback function
 */
"use strict";
const fs = require("fs");
const prompt = require("./prompt");

const Buttercup = require("buttercup");

const writeToken = (password, token) => {
  return new Promise((res, rej) => {
    let datasource = new Buttercup.FileDatasource(".git-labelmaker.token.bcup");

    let archive = Buttercup.Archive.createWithDefaults();

    datasource.save(archive, password);
    /*
    fs.writeFile( __dirname+'/../.token.json', JSON.stringify( { "token": token }, null, 2 ), 'utf8', (e)=>{
      if (e) rej(e);
      console.log("Stored new token!");
      res(token);
    })
    */
  });
};

module.exports = (done) => {
  prompt([{
      type: "input",
      name: "token",
      message: "What is your GitHub Access Token?",
      validate: (answer) => {
        return (answer !== undefined && answer.length !== 0);
      }
  }, {
    type: "password",
    name: "master_password",
    message: "What is your master password, to keep your access token secure?",
    when: (answer) => {
      return (answer.token !== undefined && answer.token.length !== 0);
    }
  }])
  .then((answer) => {
    return writeToken(answer.master_password, answer.token);
  })
  .then((token)=>{
    done(token);
  })
  .catch(console.warn);
};
<commit_msg>Store token<commit_after>/**
 *    Returns a promise that fetches the token and resolves with it if it's found
 *    @dep    {node}     fs
 *    @dep    {npm}      inquirer
 *    @param  {Function} callback function
 */
"use strict";
const fs = require("fs");
const prompt = require("./prompt");

const Buttercup = require("buttercup");

const writeToken = (password, token) => {
  return new Promise((res, rej) => {
    let datasource = new Buttercup.FileDatasource(".git-labelmaker.token.bcup");

    let archive = Buttercup.Archive.createWithDefaults();

    let group = archive.createGroup("git-labelmaker");

    group.setAttribute('token', token);

    datasource.save(archive, password);
  });
};

module.exports = (done) => {
  prompt([{
      type: "input",
      name: "token",
      message: "What is your GitHub Access Token?",
      validate: (answer) => {
        return (answer !== undefined && answer.length !== 0);
      }
  }, {
    type: "password",
    name: "master_password",
    message: "What is your master password, to keep your access token secure?",
    when: (answer) => {
      return (answer.token !== undefined && answer.token.length !== 0);
    }
  }])
  .then((answer) => {
    return writeToken(answer.master_password, answer.token);
  })
  .then((token)=>{
    done(token);
  })
  .catch(console.warn);
};
<|endoftext|>


<commit_before>let rootDir = process.cwd();
let outputDir = `${rootDir}/Distribution`;
let sourceDir = `${rootDir}/Source`;

const _rootDir = new WeakMap();
const _outputDir = new WeakMap();
const _sourceDir = new WeakMap();

class config {
    constructor() {
        this.paths = new paths(this);
    }
}

class paths {
    constructor(config) {
        this.config = config;
    }

    get html() {
        return [
            `!${this.config.rootDir}/jspm_packages/**/*`,
            `!${this.config.rootDir}/node_modules/**/*`,
            `!${this.config.outputDir}/**/*`,
            `${this.config.rootDir}/**/*.html`
        ]
    }

    get javascript() {
        return [
            `!${this.config.rootDir}/jspm_packages/**/*`,
            `!${this.config.rootDir}/node_modules/**/*`,
            `!${this.config.outputDir}/**/*`,
            `!${this.config.rootDir}/config.js`,
            `!${this.config.rootDir}/gulpfile.js`,
            `!${this.config.rootDir}/gulp/**/*`,
            `${this.config.sourceDir}/**/*.js`
        ]
    }

    get less() {
        return [
            `!${this.config.rootDir}/jspm_packages/**/*`,
            `!${this.config.rootDir}/node_modules/**/*`,
            `!${this.config.outputDir}/**/*`,
            `${this.config.rootDir}/**/*.less`
        ];
    }

    get content() {
        return [
            `!${this.config.rootDir}/node_modules/**/*`,
            `!${this.config.outputDir}/**/*`,
            `${this.config.rootDir}/jspm_packages/**/*`,
            `${this.config.rootDir}/**/*.jpg`,
            `${this.config.rootDir}/**/*.jpeg`,
            `${this.config.rootDir}/**/*.gif`,
            `${this.config.rootDir}/**/*.png`
        ]
    }

    get rootDir() {
        if (_rootDir.has(this)) {
            return _rootDir.get(this);
        }
        return _rootDir;
    }

    set rootDir(value) { _rootDir.set(this, value); }

    get outputDir() {
        if (_outputDir.has(this)) {
            return _outputDir.get(this);
        }
        return outputDir;
    }

    set outputDir(value) { _outputDir.set(this, value); }

    get sourceDir() {
        if (_sourceDir.has(this)) {
            return _sourceDir.get(this);
        }
        return sourceDir;
    }

    set sourceDir(value) { _sourceDir.set(this, value); }
}

export default new config();<commit_msg>Fixing paths - directly from this<commit_after>let rootDir = process.cwd();
let outputDir = `${rootDir}/Distribution`;
let sourceDir = `${rootDir}/Source`;

const _rootDir = new WeakMap();
const _outputDir = new WeakMap();
const _sourceDir = new WeakMap();

class config {
    constructor() {
        this.paths = new paths(this);
    }
}

class paths {
    constructor(config) {
        this.config = config;
    }

    get html() {
        return [
            `!${this.rootDir}/jspm_packages/**/*`,
            `!${this.rootDir}/node_modules/**/*`,
            `!${this.outputDir}/**/*`,
            `${this.rootDir}/**/*.html`
        ]
    }

    get javascript() {
        return [
            `!${this.rootDir}/jspm_packages/**/*`,
            `!${this.rootDir}/node_modules/**/*`,
            `!${this.outputDir}/**/*`,
            `!${this.rootDir}/config.js`,
            `!${this.rootDir}/gulpfile.js`,
            `!${this.rootDir}/gulp/**/*`,
            `${this.sourceDir}/**/*.js`
        ]
    }

    get less() {
        return [
            `!${this.rootDir}/jspm_packages/**/*`,
            `!${this.rootDir}/node_modules/**/*`,
            `!${this.outputDir}/**/*`,
            `${this.rootDir}/**/*.less`
        ];
    }

    get content() {
        return [
            `!${this.rootDir}/node_modules/**/*`,
            `!${this.outputDir}/**/*`,
            `${this.rootDir}/jspm_packages/**/*`,
            `${this.rootDir}/**/*.jpg`,
            `${this.rootDir}/**/*.jpeg`,
            `${this.rootDir}/**/*.gif`,
            `${this.rootDir}/**/*.png`
        ]
    }

    get rootDir() {
        if (_rootDir.has(this)) {
            return _rootDir.get(this);
        }
        return _rootDir;
    }

    set rootDir(value) { _rootDir.set(this, value); }

    get outputDir() {
        if (_outputDir.has(this)) {
            return _outputDir.get(this);
        }
        return outputDir;
    }

    set outputDir(value) { _outputDir.set(this, value); }

    get sourceDir() {
        if (_sourceDir.has(this)) {
            return _sourceDir.get(this);
        }
        return sourceDir;
    }

    set sourceDir(value) { _sourceDir.set(this, value); }
}

export default new config();<|endoftext|>


<commit_before><commit_msg>fix transpiling<commit_after><|endoftext|>


<commit_before><commit_msg>fixed empty spaces<commit_after><|endoftext|>


<commit_before><commit_msg>theocean wip<commit_after><|endoftext|>


<commit_before><commit_msg>Fix inverse index in N3Store being used in all cases<commit_after><|endoftext|>


<commit_before><commit_msg>updating variable names<commit_after><|endoftext|>


<commit_before>'use strict';

/*

Idea of a simple JSON-Schema bundler and dereferencer.

(https://github.com/BigstickCarpet/json-schema-ref-parser seems to be bugged.
It produces bundles that it can't dereference.)

The following algo produces a bundle that is much flatter, with shorter pointers,
and that can be dereferenced.

Format of the bundle:

{
    "1": <requested schema with internal refs>,
    "2": <additional schema with internal refs>,
    "3": <additional schema with internal refs>,
    ...
}

TODO

Use an array instead of a dictionary.
The JSON-Schema of bundle would be:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "array",
    "items": {
        "type": "http://json-schema.org/draft-04/schema#"
    }
}

*/

function Pointer(pointerStr) {
    // TODO Need normalization ? like lowercase the hostname part ? URI parser ?

    this.str = pointerStr;

    this.isInternal = this.str.substr(0, 1) === '#';

    var fragmentIndex = this.str.indexOf('#');

    this.fragment = fragmentIndex >= 0 ?
        this.str.substr(fragmentIndex + 1) : null;

    this.url = fragmentIndex >= 0 ?
        this.str.substr(0, fragmentIndex) : this.str;

    if (this.fragment !== null) {
        this.fragmentSegments = this.fragment.split('/');
        this.fragmentSegments.shift();
    }
}

// assume schema loaded from JSON documents (no circular refs)
function bundle(originPointer, originSchema, pointerResolver) {

    var bundle = {}; // the result
    var id = 0;
    var pointerToId = {};
    var queue = []; // queue of id of schemas to be processed

    var addToBundle = function (pointer, schema) {
        id++;
        pointerToId[pointer.url] = id;
        bundle[id] = JSON.parse(JSON.stringify(schema)); // deep copy
        queue.push(id);
        return id;
    };

    var recusiveBundle = function(localPointerId, schemaPart) {
        if (schemaPart['$ref']) {
            var pointer = new Pointer(schemaPart['$ref']);
            if (pointer.isInternal) {
                // repoint the ref
                var newRef = '#/' + localPointerId;
                if (pointer.fragment) {
                    newRef += pointer.fragment;
                }
                schemaPart['$ref'] = newRef;
            }
            else {
                var pointerId = pointerToId[pointer.url];
                if (!pointerId) {
                    var externalSchema = pointerResolver(pointer); // TODO promise + error handling
                    pointerId = addToBundle(pointer, externalSchema);
                }
                var newRef = '#/' + pointerId;
                if (pointer.fragment) {
                    newRef += pointer.fragment;
                }
                schemaPart['$ref'] = newRef;
            }
        }
        else {
            for (var property in schemaPart) {
                if (schemaPart.hasOwnProperty(property)) {
                    if (typeof schemaPart[property] === 'object') {
                        recusiveBundle(localPointerId, schemaPart[property]);
                    }
                }
            }
        }
    };

    addToBundle(new Pointer(originPointer), originSchema)

    while (queue.length > 0) {
        var pointerId = queue.shift();
        recusiveBundle(pointerId, bundle[pointerId]);
    };

    return bundle
}

/**
 * Resolve and internal pointer.
 * Throw an error is the pointer is not internal, or if the document part cannot be found.
 */
function resolvePointer(pointer, bundleDocument) {
    if (!pointer) {
        throw new Error('pointer required');
    }
    if (!bundleDocument) {
        throw new Error('bundleDocument required');
    }
    if (!pointer.isInternal) {
        throw new Error('not an internal pointer');
    }

    if (pointer.fragmentSegments.length === 0) {
        // "$ref": "#"
        return bundleDocument;
    }
    var documentPart = bundleDocument;
    pointer.fragmentSegments.forEach(function (segment) {
        if (documentPart.hasOwnProperty(segment)) {
            documentPart = documentPart[segment];
        }
        else {
            throw new Error('invalid JSON pointer can\'t find ' +
                pointer.str + ', segment: ' + segment);
        }
    });
    return documentPart;
}

/**
 * Dereference the bundle in place.
 * Handle internal references only, throw an error otherwise.
 * The dereferenced schema can contain circular references.
 * Intended to be used for schema bundles.
 */
function derefSchemaBundle(bundleDocument) {

    var seen = [];

    var recusiveDeref = function(parentPart, partName, documentPart) {

        // handle circular references
        if (seen.indexOf(documentPart) !== -1) {
            return;
        }
        seen.push(documentPart);

        if (documentPart['$ref']) {
            var pointer = new Pointer(documentPart['$ref']);
            var pointerData = resolvePointer(pointer, bundleDocument);
            if (parentPart) {
                parentPart[partName] = pointerData;
            }
            else {
                // the whole schema was a $ref
                documentPart = pointerData;
            }
            recusiveDeref(parentPart, partName, pointerData);
        }
        else {
            for (var property in documentPart) {
                if (documentPart.hasOwnProperty(property)) {
                    if (typeof documentPart[property] === 'object') {
                        recusiveDeref(documentPart, property, documentPart[property]);
                    }
                }
            }
        }
    };
    recusiveDeref(null, null, bundleDocument['1']);
    return bundleDocument['1'];
}

module.exports = {
    Pointer: Pointer,
    bundle: bundle,
    derefSchemaBundle: derefSchemaBundle,
};
<commit_msg>Make the format of a bundle be an array of JSON-Schema<commit_after>'use strict';

/*

Idea of a simple JSON-Schema bundler and dereferencer.

(https://github.com/BigstickCarpet/json-schema-ref-parser seems to be bugged.
It produces bundles that it can't dereference.)

The following algo produces a bundle that is much flatter, with shorter pointers,
and that can be dereferenced.

The JSON-Schema of bundle is:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "array",
    "items": {
        "type": "http://json-schema.org/draft-04/schema#"
    }
}

*/

function Pointer(pointerStr) {
    // TODO Need normalization ? like lowercase the hostname part ? URI parser ?

    this.str = pointerStr;

    this.isInternal = this.str.substr(0, 1) === '#';

    var fragmentIndex = this.str.indexOf('#');

    this.fragment = fragmentIndex >= 0 ?
        this.str.substr(fragmentIndex + 1) : null;

    this.url = fragmentIndex >= 0 ?
        this.str.substr(0, fragmentIndex) : this.str;

    if (this.fragment !== null) {
        this.fragmentSegments = this.fragment.split('/');
        this.fragmentSegments.shift();
    }
}

// assume schema loaded from JSON documents (no circular refs)
function bundle(originPointer, originSchema, pointerResolver) {

    var bundle = []; // the result
    var pointerToId = {};
    var queue = []; // queue of id of schemas to be processed

    var addToBundle = function (pointer, schema) {
        bundle.push(JSON.parse(JSON.stringify(schema))); // deep copy
        var id = bundle.length - 1;
        pointerToId[pointer.url] = id;
        queue.push(id);
        return id;
    };

    var recusiveBundle = function(localPointerId, schemaPart) {
        if (schemaPart['$ref']) {
            var pointer = new Pointer(schemaPart['$ref']);
            if (pointer.isInternal) {
                // repoint the ref
                var newRef = '#/' + localPointerId;
                if (pointer.fragment) {
                    newRef += pointer.fragment;
                }
                schemaPart['$ref'] = newRef;
            }
            else {
                var pointerId = pointerToId[pointer.url];
                if (pointerId === undefined) {
                    var externalSchema = pointerResolver(pointer); // TODO promise + error handling
                    pointerId = addToBundle(pointer, externalSchema);
                }
                var newRef = '#/' + pointerId;
                if (pointer.fragment) {
                    newRef += pointer.fragment;
                }
                schemaPart['$ref'] = newRef;
            }
        }
        else {
            for (var property in schemaPart) {
                if (schemaPart.hasOwnProperty(property)) {
                    if (typeof schemaPart[property] === 'object') {
                        recusiveBundle(localPointerId, schemaPart[property]);
                    }
                }
            }
        }
    };

    addToBundle(new Pointer(originPointer), originSchema)

    while (queue.length > 0) {
        var pointerId = queue.shift();
        recusiveBundle(pointerId, bundle[pointerId]);
    };

    return bundle
}

/**
 * Resolve and internal pointer.
 * Throw an error is the pointer is not internal, or if the document part cannot be found.
 */
function resolvePointer(pointer, bundleDocument) {
    if (!pointer) {
        throw new Error('pointer required');
    }
    if (!bundleDocument) {
        throw new Error('bundleDocument required');
    }
    if (!pointer.isInternal) {
        throw new Error('not an internal pointer');
    }

    if (pointer.fragmentSegments.length === 0) {
        // "$ref": "#"
        return bundleDocument;
    }
    var documentPart = bundleDocument;
    pointer.fragmentSegments.forEach(function (segment) {
        if (documentPart.hasOwnProperty(segment)) {
            documentPart = documentPart[segment];
        }
        else {
            throw new Error('invalid JSON pointer can\'t find ' +
                pointer.str + ', segment: ' + segment);
        }
    });
    return documentPart;
}

/**
 * Dereference the bundle in place.
 * Handle internal references only, throw an error otherwise.
 * The dereferenced schema can contain circular references.
 * Intended to be used for schema bundles.
 */
function derefSchemaBundle(bundleDocument) {

    var seen = [];

    var recusiveDeref = function(parentPart, partName, documentPart) {

        // handle circular references
        if (seen.indexOf(documentPart) !== -1) {
            return;
        }
        seen.push(documentPart);

        if (documentPart['$ref']) {
            var pointer = new Pointer(documentPart['$ref']);
            var pointerData = resolvePointer(pointer, bundleDocument);
            if (parentPart) {
                parentPart[partName] = pointerData;
            }
            else {
                // the whole schema was a $ref
                documentPart = pointerData;
            }
            recusiveDeref(parentPart, partName, pointerData);
        }
        else {
            for (var property in documentPart) {
                if (documentPart.hasOwnProperty(property)) {
                    if (typeof documentPart[property] === 'object') {
                        recusiveDeref(documentPart, property, documentPart[property]);
                    }
                }
            }
        }
    };
    recusiveDeref(null, null, bundleDocument[0]);
    return bundleDocument[0];
}

module.exports = {
    Pointer: Pointer,
    bundle: bundle,
    derefSchemaBundle: derefSchemaBundle,
};
<|endoftext|>


<commit_before><commit_msg>Added SOAP_TERMINATE_SUBSCRIPTION<commit_after><|endoftext|>


<commit_before>/**
 * @file
 */
/**
 * @module checker
 * @requires path
 * @requires child_process
 * @requires url
 * @requires http
 * @requires headless
 * @requires ejs
 * @requires fs
 */
var path = require('path'),
    proc = require('child_process'),
    url = require('url'),
    http = require('http'),
    headless = require('headless'),
    ejs = require('ejs'),
    fs = require('fs'),
    browserEmulator = require('mobile-web-browser-emulator');

var filters = rootRequire('lib/ejs-filters');

filters.loadFilters(ejs);

/**
 * @class
 */
var Checker = function() {};

/**
 * @augments Checker
 * @param {Object} options
 * @param {String} options.url URL for a document to load
 * @param {String} options.profile profile of the device selected
 * @param {Array} options.checklist array of checks to do
 * @param {} options.events where to send the various events
 * @param {Number} options.id generated id to save an unique screenshot
 */
Checker.prototype.check = function(options) {
    if (!options.events) return this.throw(
        "The events option is required for reporting.");
    if (!options.url) return this.throw(options.events,
        "Without url there is nothing to check.");
    if (!options.checklist) return this.throw(options.events,
        "Without checklist there is nothing to check");
    if (!options.id) return this.throw(options.events,
        "Without id, the app can't generate screenshot");

    var self = this,
        sink = options.events,
        checklist = options.checklist,
        resume = {
            ok: 0,
            issues: 0
        };
    self.sink = sink;
    self.checklist = checklist;
    self.resume = resume;
    self.options = options;
    self.formatReport = options.formatReport || reportFormatter;
    self.id = options.id;

    fs.readdir(path.join(__dirname, "profiles"), function(err, files) {
        var validProfiles = files;
        if (!options.profile || validProfiles.indexOf(options.profile + ".json") === -1) {
            options.profile = 'default';
        }
        var profile = require('./profiles/' + options.profile + '.json');
        self.profile = profile;

        self.emulateBrowser(profile, options.url);
    });

};
/**
 * @augments Checker
 * @param {Object} profile device's profile
 * @param {Number} profile.width device's width
 * @param {Number} profile.height device's height
 * @param {String} url URL for a document to load
 */
Checker.prototype.emulateBrowser = function(profile, url) {
    var self = this;
    var opt = {
        display: {
            width: profile.width,
            height: profile.height
        }
    };
    headless(opt, function(err, childProcess, servernum) {
        var Browser = browserEmulator.Browser;
        var browser = new Browser({
            browserWidth: profile.width,
            browserHeight: profile.height,
            uaHeader: 'Mozilla/5.0 (Linux; Android 4.4.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/36.0.1025.133 Mobile Safari/535.19',
            displayServer: servernum,
            browsermobProxy: {
                port: 8080
            },
            trackNetwork: true
        });
        browser.open(url);
        browser.on('error', function(err) {
            console.log(err);
        });
        browser.on('screenshot', function(path) {
            self.sink.emit('screenshot', path.substring(7));
        });
        browser.on('done', function() {
            self.sink.emit('end');
        });
        self.launchChecklist(browser);
        browser.close();
    });
};
/**
 * @augments Checker
 * @param {Object} browser browser object which emulate a mobile browser
 */
Checker.prototype.launchChecklist = function(browser) {
    for (var i = 0; i < this.checklist.length; i++)
        this.checklist[i].check(this, browser);
};
/**
 * @augments Checker
 * @param {String} key represent the key of the send issue
 * @param {String} name name of the done check
 * @param {String} category of the concerned check
 * @param {Object} data optional data send with check result
 */
Checker.prototype.report = function(key, name, category, status, data) {
    this.resume[name] = key;
    if (key == "ok") {
        this.resume.ok++;
        this.sink.emit('done');
    } else {
        this.resume.issues++;
        var intl = require("intl");
        if (!data) {
            data = {};
        }
        this.sink.emit('err', {
            "issue": this.formatReport(key, name, category, data),
            "status": status
        });
        this.sink.emit('done');
    }
};

/**
 * @augments Checker
 * @param {String} key represent the key of the send issue
 * @param {String} name name of the done check
 * @param {String} category of the concerned check
 * @param {Object} data optional data send with check result
 */
function reportFormatter(key, name, category, data) {
    var str = fs.readFileSync(path.join(__dirname, '/issues/' + category + '/' +
        name + '/' + key + '.ejs'), 'utf8');
    data = data || {};
    data.name = name;
    data.category = category;
    return ejs.render(str, data);
}
/**
 * @augments Checker
 * @param {} sink events
 * @param {String} msg error message to send to client console via sink event
 */
Checker.prototype.throw = function(sink, msg) {
    if (!sink) return console.error(
        "[BOOM] No event sink with which to report system errors.\nAlso: " +
        msg);
    sink.emit("exception", msg);
};

exports.Checker = Checker;<commit_msg>fix orphans processes issues<commit_after>/**
 * @file
 */
/**
 * @module checker
 * @requires path
 * @requires child_process
 * @requires url
 * @requires http
 * @requires headless
 * @requires ejs
 * @requires fs
 */
var path = require('path'),
    proc = require('child_process'),
    url = require('url'),
    http = require('http'),
    headless = require('headless'),
    ejs = require('ejs'),
    fs = require('fs'),
    browserEmulator = require('mobile-web-browser-emulator');

var filters = rootRequire('lib/ejs-filters');

filters.loadFilters(ejs);

/**
 * @class
 */
var Checker = function() {};

/**
 * @augments Checker
 * @param {Object} options
 * @param {String} options.url URL for a document to load
 * @param {String} options.profile profile of the device selected
 * @param {Array} options.checklist array of checks to do
 * @param {} options.events where to send the various events
 * @param {Number} options.id generated id to save an unique screenshot
 */
Checker.prototype.check = function(options) {
    if (!options.events) return this.throw(
        "The events option is required for reporting.");
    if (!options.url) return this.throw(options.events,
        "Without url there is nothing to check.");
    if (!options.checklist) return this.throw(options.events,
        "Without checklist there is nothing to check");
    if (!options.id) return this.throw(options.events,
        "Without id, the app can't generate screenshot");

    var self = this,
        sink = options.events,
        checklist = options.checklist,
        resume = {
            ok: 0,
            issues: 0
        };
    self.sink = sink;
    self.checklist = checklist;
    self.resume = resume;
    self.options = options;
    self.formatReport = options.formatReport || reportFormatter;
    self.id = options.id;

    fs.readdir(path.join(__dirname, "profiles"), function(err, files) {
        var validProfiles = files;
        if (!options.profile || validProfiles.indexOf(options.profile + ".json") === -1) {
            options.profile = 'default';
        }
        var profile = require('./profiles/' + options.profile + '.json');
        self.profile = profile;

        self.emulateBrowser(profile, options.url);
    });

};
/**
 * @augments Checker
 * @param {Object} profile device's profile
 * @param {Number} profile.width device's width
 * @param {Number} profile.height device's height
 * @param {String} url URL for a document to load
 */
Checker.prototype.emulateBrowser = function(profile, url) {
    var self = this;
    var opt = {
        display: {
            width: profile.width,
            height: profile.height
        }
    };
    headless(opt, function(err, childProcess, servernum) {
        var Browser = browserEmulator.Browser;
        var browser = new Browser({
            browserWidth: profile.width,
            browserHeight: profile.height,
            uaHeader: 'Mozilla/5.0 (Linux; Android 4.4.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/36.0.1025.133 Mobile Safari/535.19',
            displayServer: servernum,
            browsermobProxy: {
                port: 8080
            },
            trackNetwork: true
        });

        browser.open(url);
        browser.on('error', function(err) {
            console.log(err);
            browser.close();
            childProcess.kill();
        });
        browser.on('screenshot', function(path) {
            self.sink.emit('screenshot', path.substring(7));
        });
        browser.on('done', function() {
            self.sink.emit('end');
        });
        self.launchChecklist(browser);
        browser.close();
        childProcess.kill();
    });
};
/**
 * @augments Checker
 * @param {Object} browser browser object which emulate a mobile browser
 */
Checker.prototype.launchChecklist = function(browser) {
    for (var i = 0; i < this.checklist.length; i++)
        this.checklist[i].check(this, browser);
};
/**
 * @augments Checker
 * @param {String} key represent the key of the send issue
 * @param {String} name name of the done check
 * @param {String} category of the concerned check
 * @param {Object} data optional data send with check result
 */
Checker.prototype.report = function(key, name, category, status, data) {
    this.resume[name] = key;
    if (key == "ok") {
        this.resume.ok++;
        this.sink.emit('done');
    } else {
        this.resume.issues++;
        var intl = require("intl");
        if (!data) {
            data = {};
        }
        this.sink.emit('err', {
            "issue": this.formatReport(key, name, category, data),
            "status": status
        });
        this.sink.emit('done');
    }
};

/**
 * @augments Checker
 * @param {String} key represent the key of the send issue
 * @param {String} name name of the done check
 * @param {String} category of the concerned check
 * @param {Object} data optional data send with check result
 */
function reportFormatter(key, name, category, data) {
    var str = fs.readFileSync(path.join(__dirname, '/issues/' + category + '/' +
        name + '/' + key + '.ejs'), 'utf8');
    data = data || {};
    data.name = name;
    data.category = category;
    return ejs.render(str, data);
}
/**
 * @augments Checker
 * @param {} sink events
 * @param {String} msg error message to send to client console via sink event
 */
Checker.prototype.throw = function(sink, msg) {
    if (!sink) return console.error(
        "[BOOM] No event sink with which to report system errors.\nAlso: " +
        msg);
    sink.emit("exception", msg);
};

exports.Checker = Checker;<|endoftext|>