<commit_before>const muxrpc = require('muxrpc')
const handshake = require("./handshake")
const _ = require('icebreaker')
const traverse = require('traverse');
const clone = traverse.clone
const Remote = require('./remote')
const Util = require('muxrpc/util')
const promiseToCallback = require('promise-to-callback');

function toManifest(api) {
  const manifest = traverse.clone(api)
  traverse(manifest).forEach(function (x) {
    if (_.isPlainObject(x) && _.isString(x.type)) {
      if (x.type === "sink") x.type = "duplex"
      if (x.type === "promise") x.type = "async"
      this.update(x.type)
    }
  })
  return manifest
}

module.exports = function RPC(_local, opts, cb) {
  let local
  if (_local) {
    local = clone(_local);
    traverse(local).forEach(function (x) {
      if (_.isFunction(x)) {
        const obj = {};
        for (let i in x) obj[i] = x[i]
        this.update(obj)
      }
    })
    _local = clone(_local)
    traverse(_local).forEach(function (x) {
      if (_.isFunction(x) && x.type === "promise") {
        const obj = function (...args) {
          const cb = args.length > 0 && _.isFunction(args[args.length - 1]) ? args.pop() : null
          promiseToCallback(x(...args))(function (err, data) {
            if (cb) cb(err, data)
          });

        }
        for (let i in x) obj[i] = x[i]
        this.update(obj)
      }
      else if (_.isFunction(x) && x.type === "sink") {
        const obj = function (...args) {
          const p = _.pushable((err) => { if (cb) { cb(err); cb = null } })
          let cb = p.end
          return {
            source: p,
            sink: x(...args, function (err) {
              if (cb != null) {
                cb(err);
                cb = null;
                return;
              }

            })
          }
        };
        for (let i in x) obj[i] = x[i]
        this.update(obj)
      }
    })
  }

  if (_.isFunction(opts)) {
    cb = opts
    opts = {}
  }

  if (!_.isFunction(cb)) throw new Error("cb is required")

  opts = opts || {}
  if (opts.isClient == null) throw new Error("opts.isClient is required")

  let wrap =  _.isFunction(opts.wrap)?opts.wrap:undefined
  delete opts.wrap

  let abortCB
  let abortError

  let muxer
  const shake = handshake(local,function(err, rest, remote){
   if(err) { return cb(err) }
    muxer = muxrpc(toManifest(remote), toManifest(local), _local, null, opts.perms)

    shake.end =  function(err,cb){
      if(_.isFunction(err)){
        cb=err
        err=null
      }
      if(rest.end) return rest.end(err||true,cb||function(){ })
      return muxer.stream.close(err||true,cb||function(){ })
    }

    if(wrap) rest = wrap(Object.assign({end:(err,cb)=>{
      return muxer.stream.close(err||true,cb||function(){ })
    }},rest))

    _(rest,muxer.stream, rest)

    let r = Remote(remote, (type, path, args) => {

      if (type === "promise") {
        return new Promise(function (resolve, reject) {
          const cb2 = function (err, data) {
            if (err) return reject(err)
            return resolve(data)
          }
          muxer.stream.remoteCall("async", path, args, cb2)
        });
      }

      const cb2 = _.isFunction(args[args.length - 1]) ? args.pop() : (err) => {
        if (err) throw new Error('callback not provided')
      }
      try {

        if (muxer.stream.closed) throw new Error('stream is closed')
        if (type === "sink") {
          let d = muxer.stream.remoteCall("duplex", path, args)
          _(d.source, _.onEnd(cb2))
          return d.sink
        }

        return muxer.stream.remoteCall(type, path, args, cb2)
      }
      catch (err) {
        return Util.errorAsStreamOrCb(type, err, cb2)
      }
    });

    cb(err, r)
  }, opts.isClient,opts.timeout)

  return shake
}

<commit_msg>fix for shake.end<commit_after>const muxrpc = require('muxrpc')
const handshake = require("./handshake")
const _ = require('icebreaker')
const traverse = require('traverse');
const clone = traverse.clone
const Remote = require('./remote')
const Util = require('muxrpc/util')
const promiseToCallback = require('promise-to-callback');

function toManifest(api) {
  const manifest = traverse.clone(api)
  traverse(manifest).forEach(function (x) {
    if (_.isPlainObject(x) && _.isString(x.type)) {
      if (x.type === "sink") x.type = "duplex"
      if (x.type === "promise") x.type = "async"
      this.update(x.type)
    }
  })
  return manifest
}

module.exports = function RPC(_local, opts, cb) {
  let local
  if (_local) {
    local = clone(_local);
    traverse(local).forEach(function (x) {
      if (_.isFunction(x)) {
        const obj = {};
        for (let i in x) obj[i] = x[i]
        this.update(obj)
      }
    })
    _local = clone(_local)
    traverse(_local).forEach(function (x) {
      if (_.isFunction(x) && x.type === "promise") {
        const obj = function (...args) {
          const cb = args.length > 0 && _.isFunction(args[args.length - 1]) ? args.pop() : null
          promiseToCallback(x(...args))(function (err, data) {
            if (cb) cb(err, data)
          });

        }
        for (let i in x) obj[i] = x[i]
        this.update(obj)
      }
      else if (_.isFunction(x) && x.type === "sink") {
        const obj = function (...args) {
          const p = _.pushable((err) => { if (cb) { cb(err); cb = null } })
          let cb = p.end
          return {
            source: p,
            sink: x(...args, function (err) {
              if (cb != null) {
                cb(err);
                cb = null;
                return;
              }

            })
          }
        };
        for (let i in x) obj[i] = x[i]
        this.update(obj)
      }
    })
  }

  if (_.isFunction(opts)) {
    cb = opts
    opts = {}
  }

  if (!_.isFunction(cb)) throw new Error("cb is required")

  opts = opts || {}
  if (opts.isClient == null) throw new Error("opts.isClient is required")

  let wrap =  _.isFunction(opts.wrap)?opts.wrap:undefined
  delete opts.wrap

  let abortCB
  let abortError

  let muxer
  const _cbs = []
  const shake = handshake(local,function(err, rest, remote){
   if(err) {
      while(_cbs.length>0)_cbs.shift().call(null,err)
     return cb(err)
    }
    muxer = muxrpc(toManifest(remote), toManifest(local), _local, null, opts.perms)

    shake.end =  function(err,cb){
      if(_.isFunction(err)){
        cb=err
        err=null
      }
      if(rest.end) return rest.end(err||true,cb||function(){ })
      return muxer.stream.close(err||true,cb||function(){ })
    }

    if(wrap) rest = wrap(Object.assign({end:(err,cb)=>{
      return muxer.stream.close(err||true,cb||function(){ })
    }},rest))

    _(rest,muxer.stream, rest)

    let r = Remote(remote, (type, path, args) => {

      if (type === "promise") {
        return new Promise(function (resolve, reject) {
          const cb2 = function (err, data) {
            if (err) return reject(err)
            return resolve(data)
          }
          muxer.stream.remoteCall("async", path, args, cb2)
        });
      }

      const cb2 = _.isFunction(args[args.length - 1]) ? args.pop() : (err) => {
        if (err) throw new Error('callback not provided')
      }
      try {

        if (muxer.stream.closed) throw new Error('stream is closed')
        if (type === "sink") {
          let d = muxer.stream.remoteCall("duplex", path, args)
          _(d.source, _.onEnd(cb2))
          return d.sink
        }

        return muxer.stream.remoteCall(type, path, args, cb2)
      }
      catch (err) {
        return Util.errorAsStreamOrCb(type, err, cb2)
      }
    });

    cb(err, r)
  }, opts.isClient,opts.timeout)

  shake.end = (err,cb)=>{
    _cbs.push(cb)
    shake.abort(err)
  }

  return shake
}

<|endoftext|>


<commit_before>'use babel';

// @flow

import { CompositeDisposable } from 'atom';
import type { Panel, TextEditor } from 'atom';
import Commands from './commands';
import EditorsRegistry from './editor-registry';
import TesterRegistry from './tester-registry';
import { clearInlineMessages, decorateGutter, setInlineMessages } from './decorate-manager';
import OutputView from './views/OutputView';
import type { State, Tester as TesterProvider, Message } from './types';

class Tester {
  state: any;
  commands: Commands;
  subscriptions: CompositeDisposable;
  modalPanel: Panel;
  registryTesters: TesterRegistry;
  registryEditors: EditorsRegistry;
  statusBarTile: Object;
  markers: Array<Object>;
  showInlineError: boolean;
  inlineErrorPosition: string;
  ansiToHtml: boolean;
  showOutputAfterTestRun: boolean;
  showNotifications: boolean;
  messageTimeout: number;
  runningTestersCount: number;
  scrollToBottom: boolean;
  panel: ?OutputView;
  messages:Array<Message>;
  output: string;
  testerStatusBar: Object;

  constructor(state : State) {
    this.state = state;
    this.commands = new Commands();
    this.registryEditors = new EditorsRegistry();
    this.registryTesters = new TesterRegistry();
    this.subscriptions = new CompositeDisposable();
    this.subscriptions.add(this.commands);
    this.subscriptions.add(this.registryEditors);
    this.subscriptions.add(this.registryTesters);
    this.markers = [];
    this.showInlineError = true;
    this.inlineErrorPosition = 'after';
    this.ansiToHtml = true;
    this.showOutputAfterTestRun = true;
    this.showNotifications = true;
    this.messageTimeout = 5;
    this.runningTestersCount = 0;
    this.scrollToBottom = true;
    this.messages = [];
    this.output = 'Nothing new to show';
    const self = this;

    this.subscriptions.add(atom.config.observe('tester.gutterEnabled', (gutterEnabled) => {
      this.registryEditors.gutterEnabled = gutterEnabled;
    }));
    this.subscriptions.add(atom.config.observe('tester.showInlineError', (showInlineError) => {
      this.showInlineError = showInlineError;
    }));
    this.subscriptions.add(atom.config.observe('tester.inlineErrorPosition', (inlineErrorPosition) => {
      this.inlineErrorPosition = inlineErrorPosition;
    }));
    this.subscriptions.add(atom.config.observe('tester.ansiToHtml', (ansiToHtml) => {
      this.ansiToHtml = ansiToHtml;
    }));
    this.subscriptions.add(atom.config.observe('tester.showOutputAfterTestRun', (showOutputAfterTestRun) => {
      this.showOutputAfterTestRun = showOutputAfterTestRun;
    }));
    this.subscriptions.add(atom.config.observe('tester.messageTimeout', (messageTimeout) => {
      this.messageTimeout = messageTimeout;
    }));
    this.subscriptions.add(atom.config.observe('tester.showNotifications', (showNotifications) => {
      this.showNotifications = showNotifications;
    }));
    this.subscriptions.add(atom.config.observe('tester.scrollToBottom', (scrollToBottom) => {
      this.scrollToBottom = scrollToBottom;
    }));
    this.subscriptions.add(atom.workspace.observeTextEditors((editor) => {
      if (self.statusBarTile) {
        self.statusBarTile.resetResults(editor);
      }
    }));
    this.subscriptions.add(atom.workspace.onDidChangeActivePaneItem((paneItem) => {
      const isTextEditor = atom.workspace.isTextEditor(paneItem);
      if (self.statusBarTile && isTextEditor) {
        self.testerStatusBar.style.display = 'block';
        self.statusBarTile.resetResults(paneItem);
      } else {
        self.testerStatusBar.style.display = 'none';
      }
    }));

    this.commands.onShouldTest(() => {
      const editorTester = self.registryEditors.get(atom.workspace.getActiveTextEditor());
      if (editorTester) {
        if (self.statusBarTile) {
          self.statusBarTile.testRunning();
          // OutputViewManager.getView().addClass('loading');
          self.statusBarTile.resetResults(atom.workspace.getActiveTextEditor());
        }
        editorTester.test();
      }
    });

    this.commands.onShouldStop(() => {
      const editorTester = self.registryEditors.get(atom.workspace.getActiveTextEditor());
      if (editorTester) {
        editorTester.stop();
      }
    });

    this.commands.onShouldToggleTesterOutput(() => {
      // OutputViewManager.getView().toggle();
      this.togglePanel();
    });

    this.registryEditors.observe((editorTester) => {
      editorTester.onShouldTest(() => {
        self.registryTesters.test(editorTester);
      });
      editorTester.onShouldTestAllOpened(() => {
        atom.workspace.getTextEditors().forEach((editor) => {
          const openedEditorTester = self.registryEditors.get(editor);
          if (openedEditorTester) {
            openedEditorTester.test();
          }
        });
      });
      editorTester.onShouldStop(() => {
        self.registryTesters.stop(editorTester);
      });
    });

    this.registryTesters.onDidUpdateMessages(({ editor, messages, output }) => {
      self.statusBarTile.setResults(editor, messages);
      const gutter = self.registryEditors.handleGutter(editor);
      if (!gutter) {
        return;
      }
      clearInlineMessages(editor);
      decorateGutter(editor, gutter, messages);
      if (this.showInlineError) {
        setInlineMessages(editor, messages, this.ansiToHtml, this.inlineErrorPosition);
      }
      this.output = output;
      this.setOutput(editor, output);
    });

    this.registryTesters.onDidBeginTesting(() => {
      if (self.statusBarTile) {
        self.statusBarTile.testRunning();
        // OutputViewManager.getView().addClass('loading');
      }
      // OutputViewManager.getView().clear();
    });

    this.registryTesters.onDidFinishTesting(() => {
      self.statusBarTile.testFinished();
      // OutputViewManager.getView().removeClass('loading');
    });

    this.registryEditors.activate();

    setTimeout(() => {
      // NOTE: Atom triggers this on boot so wait a while
      if (!self.subscriptions.disposed) {
        self.subscriptions.add(atom.project.onDidChangePaths(() => {
          self.registryEditors.handleGutter();
          self.commands.test();
        }));
      }
    }, 100);
  }

  createStatusBar(statusBar : Object) {
    const self = this;
    const baseClasses = 'tester-bottom-status highlight';
    this.testerStatusBar = document.createElement('div');
    this.testerStatusBar.classList.add('status-bar-tester', 'inline-block');
    this.testerStatusBar.style.display = 'none';

    const passedTestsSpan = document.createElement('span');
    passedTestsSpan.textContent = '0';
    passedTestsSpan.classList.add(baseClasses, 'tester-status-passed');

    const failedTestsSpan = document.createElement('span');
    failedTestsSpan.textContent = '0';
    failedTestsSpan.classList.add(baseClasses, 'tester-status-failed');

    const skippedTestsSpan = document.createElement('span');
    skippedTestsSpan.textContent = '0';
    skippedTestsSpan.classList.add(baseClasses, 'tester-status-skipped');

    const statusTiny = document.createElement('span');
    statusTiny.textContent = '';
    statusTiny.classList.add('tester-tiny', 'loading', 'loading-spinner-tiny', 'inline-block', 'idle');
    this.testerStatusBar.appendChild(failedTestsSpan);
    this.testerStatusBar.appendChild(skippedTestsSpan);
    this.testerStatusBar.appendChild(passedTestsSpan);
    this.testerStatusBar.appendChild(statusTiny);
    this.testerStatusBar.on('click', () => this.togglePanel());

    atom.tooltips.add(this.testerStatusBar, {
      title: 'failed | skipped | passed
Click to toggle the tester console output view.',
    });

    const statusBarTile = statusBar.addLeftTile({
      item: this.testerStatusBar,
      priority: -50,
    });

    statusBarTile.status = {};
    statusBarTile.status.passed = passedTestsSpan;
    statusBarTile.status.failed = failedTestsSpan;
    statusBarTile.status.skipped = skippedTestsSpan;
    statusBarTile.status.statusTiny = statusTiny;
    statusBarTile.testRunning = function testRunning() {
      self.runningTestersCount += 1;
      this.status.statusTiny.removeClass('idle');
    };
    statusBarTile.testFinished = function testFinished() {
      self.runningTestersCount -= 1;
      if (!(self.runningTestersCount > 0)) {
        this.status.statusTiny.addClass('idle');
      }
    };

    statusBarTile.resetResults = function resetResults(editor) {
      if (editor && editor.testerLastResults) {
        this.setResults(editor, editor.testerLastResults);
      } else {
        this.status.passed.text('0').removeClass('highlight-success');
        this.status.failed.text('0').removeClass('highlight-error');
        this.status.skipped.text('0').removeClass('highlight-warning');
      }
    };

    statusBarTile.setResults = function setResults(editor, results) {
      if (editor && results) {
        editor.testerLastResults = results;
      }
      if (results && editor !== atom.workspace.getActiveTextEditor() && self.showNotifications) {
        const passedTests = results.filter(result => result.state === 'passed').length;
        const failedTests = results.filter(result => result.state === 'failed').length;
        const skippedTests = results.filter(result => result.state === 'skipped').length;
        const resultMessage = `${failedTests} failed
                               ${skippedTests} skipped
                               ${passedTests} passed`;
        atom.notifications.addInfo(`<b>Tester:</b> Results for <b>${editor.getTitle()}</b><br/>${resultMessage}`, { icon: 'checklist' });
      }
      if (!results || editor !== atom.workspace.getActiveTextEditor()) {
        this.resetResults();
      } else {
        const passedTests = results.filter(result => result.state === 'passed').length;
        this.status.passed.text(passedTests);
        if (passedTests > 0) {
          this.status.passed.addClass('highlight-success');
        } else {
          this.status.passed.removeClass('highlight-success');
        }

        const failedTests = results.filter(result => result.state === 'failed').length;
        this.status.failed.text(failedTests);
        if (failedTests > 0) {
          this.status.failed.addClass('highlight-error');
        } else {
          this.status.failed.removeClass('highlight-error');
        }

        const skippedTests = results.filter(result => result.state === 'skipped').length;
        this.status.skipped.text(skippedTests);
        if (skippedTests > 0) {
          this.status.skipped.addClass('highlight-warning');
        } else {
          this.status.skipped.removeClass('highlight-warning');
        }
      }
    };
    this.statusBarTile = statusBarTile;
    this.statusBarTile.getItem().style.display = 'block';
  }

  async setOutput(editor : TextEditor, output :string) {
    if (this.panel) {
      await this.panel.update({ output });
    }
    // OutputViewManager.getView().setContent(output, this.ansiToHtml).finish(this.showOutputAfterTestRun, this.messageTimeout, this.scrollToBottom);
  }

  async togglePanel() {
    if (!this.panel) {
      this.panel = new OutputView({
        output: this.output,
        ansiToHtml: this.ansiToHtml,
        scrollToBottom: this.scrollToBottom });
    } else if (this.panel) {
      await this.panel.destroy();
      this.panel = null;
    }
  }

  dispose() {
    if (this.statusBarTile) {
      this.statusBarTile.destroy();
    }
    this.subscriptions.dispose();
  }

  addTester(tester : TesterProvider) {
    this.registryTesters.addTester(tester);
  }

  deleteTester(tester : TesterProvider) {
    this.registryTesters.deleteTester(tester);
  }
}

module.exports = Tester;
<commit_msg>fix add class<commit_after>'use babel';

// @flow

import { CompositeDisposable } from 'atom';
import type { Panel, TextEditor } from 'atom';
import Commands from './commands';
import EditorsRegistry from './editor-registry';
import TesterRegistry from './tester-registry';
import { clearInlineMessages, decorateGutter, setInlineMessages } from './decorate-manager';
import OutputView from './views/OutputView';
import type { State, Tester as TesterProvider, Message } from './types';

class Tester {
  state: any;
  commands: Commands;
  subscriptions: CompositeDisposable;
  modalPanel: Panel;
  registryTesters: TesterRegistry;
  registryEditors: EditorsRegistry;
  statusBarTile: Object;
  markers: Array<Object>;
  showInlineError: boolean;
  inlineErrorPosition: string;
  ansiToHtml: boolean;
  showOutputAfterTestRun: boolean;
  showNotifications: boolean;
  messageTimeout: number;
  runningTestersCount: number;
  scrollToBottom: boolean;
  panel: ?OutputView;
  messages:Array<Message>;
  output: string;
  testerStatusBar: Object;

  constructor(state : State) {
    this.state = state;
    this.commands = new Commands();
    this.registryEditors = new EditorsRegistry();
    this.registryTesters = new TesterRegistry();
    this.subscriptions = new CompositeDisposable();
    this.subscriptions.add(this.commands);
    this.subscriptions.add(this.registryEditors);
    this.subscriptions.add(this.registryTesters);
    this.markers = [];
    this.showInlineError = true;
    this.inlineErrorPosition = 'after';
    this.ansiToHtml = true;
    this.showOutputAfterTestRun = true;
    this.showNotifications = true;
    this.messageTimeout = 5;
    this.runningTestersCount = 0;
    this.scrollToBottom = true;
    this.messages = [];
    this.output = 'Nothing new to show';
    const self = this;

    this.subscriptions.add(atom.config.observe('tester.gutterEnabled', (gutterEnabled) => {
      this.registryEditors.gutterEnabled = gutterEnabled;
    }));
    this.subscriptions.add(atom.config.observe('tester.showInlineError', (showInlineError) => {
      this.showInlineError = showInlineError;
    }));
    this.subscriptions.add(atom.config.observe('tester.inlineErrorPosition', (inlineErrorPosition) => {
      this.inlineErrorPosition = inlineErrorPosition;
    }));
    this.subscriptions.add(atom.config.observe('tester.ansiToHtml', (ansiToHtml) => {
      this.ansiToHtml = ansiToHtml;
    }));
    this.subscriptions.add(atom.config.observe('tester.showOutputAfterTestRun', (showOutputAfterTestRun) => {
      this.showOutputAfterTestRun = showOutputAfterTestRun;
    }));
    this.subscriptions.add(atom.config.observe('tester.messageTimeout', (messageTimeout) => {
      this.messageTimeout = messageTimeout;
    }));
    this.subscriptions.add(atom.config.observe('tester.showNotifications', (showNotifications) => {
      this.showNotifications = showNotifications;
    }));
    this.subscriptions.add(atom.config.observe('tester.scrollToBottom', (scrollToBottom) => {
      this.scrollToBottom = scrollToBottom;
    }));
    this.subscriptions.add(atom.workspace.observeTextEditors((editor) => {
      if (self.statusBarTile) {
        self.statusBarTile.resetResults(editor);
      }
    }));
    this.subscriptions.add(atom.workspace.onDidChangeActivePaneItem((paneItem) => {
      const isTextEditor = atom.workspace.isTextEditor(paneItem);
      if (self.statusBarTile && isTextEditor) {
        self.testerStatusBar.style.display = 'block';
        self.statusBarTile.resetResults(paneItem);
      } else {
        self.testerStatusBar.style.display = 'none';
      }
    }));

    this.commands.onShouldTest(() => {
      const editorTester = self.registryEditors.get(atom.workspace.getActiveTextEditor());
      if (editorTester) {
        if (self.statusBarTile) {
          self.statusBarTile.testRunning();
          // OutputViewManager.getView().addClass('loading');
          self.statusBarTile.resetResults(atom.workspace.getActiveTextEditor());
        }
        editorTester.test();
      }
    });

    this.commands.onShouldStop(() => {
      const editorTester = self.registryEditors.get(atom.workspace.getActiveTextEditor());
      if (editorTester) {
        editorTester.stop();
      }
    });

    this.commands.onShouldToggleTesterOutput(() => {
      // OutputViewManager.getView().toggle();
      this.togglePanel();
    });

    this.registryEditors.observe((editorTester) => {
      editorTester.onShouldTest(() => {
        self.registryTesters.test(editorTester);
      });
      editorTester.onShouldTestAllOpened(() => {
        atom.workspace.getTextEditors().forEach((editor) => {
          const openedEditorTester = self.registryEditors.get(editor);
          if (openedEditorTester) {
            openedEditorTester.test();
          }
        });
      });
      editorTester.onShouldStop(() => {
        self.registryTesters.stop(editorTester);
      });
    });

    this.registryTesters.onDidUpdateMessages(({ editor, messages, output }) => {
      self.statusBarTile.setResults(editor, messages);
      const gutter = self.registryEditors.handleGutter(editor);
      if (!gutter) {
        return;
      }
      clearInlineMessages(editor);
      decorateGutter(editor, gutter, messages);
      if (this.showInlineError) {
        setInlineMessages(editor, messages, this.ansiToHtml, this.inlineErrorPosition);
      }
      this.output = output;
      this.setOutput(editor, output);
    });

    this.registryTesters.onDidBeginTesting(() => {
      if (self.statusBarTile) {
        self.statusBarTile.testRunning();
        // OutputViewManager.getView().addClass('loading');
      }
      // OutputViewManager.getView().clear();
    });

    this.registryTesters.onDidFinishTesting(() => {
      self.statusBarTile.testFinished();
      // OutputViewManager.getView().removeClass('loading');
    });

    this.registryEditors.activate();

    setTimeout(() => {
      // NOTE: Atom triggers this on boot so wait a while
      if (!self.subscriptions.disposed) {
        self.subscriptions.add(atom.project.onDidChangePaths(() => {
          self.registryEditors.handleGutter();
          self.commands.test();
        }));
      }
    }, 100);
  }

  createStatusBar(statusBar : Object) {
    const self = this;
    this.testerStatusBar = document.createElement('div');
    this.testerStatusBar.classList.add('status-bar-tester', 'inline-block');
    this.testerStatusBar.style.display = 'none';

    const passedTestsSpan = document.createElement('span');
    passedTestsSpan.textContent = '0';
    passedTestsSpan.classList.add('tester-bottom-status', 'highlight', 'tester-status-passed');

    const failedTestsSpan = document.createElement('span');
    failedTestsSpan.textContent = '0';
    failedTestsSpan.classList.add('tester-bottom-status', 'highlight', 'tester-status-failed');

    const skippedTestsSpan = document.createElement('span');
    skippedTestsSpan.textContent = '0';
    skippedTestsSpan.classList.add('tester-bottom-status', 'highlight', 'tester-status-skipped');

    const statusTiny = document.createElement('span');
    statusTiny.textContent = '';
    statusTiny.classList.add('tester-tiny', 'loading', 'loading-spinner-tiny', 'inline-block', 'idle');
    this.testerStatusBar.appendChild(failedTestsSpan);
    this.testerStatusBar.appendChild(skippedTestsSpan);
    this.testerStatusBar.appendChild(passedTestsSpan);
    this.testerStatusBar.appendChild(statusTiny);
    this.testerStatusBar.on('click', () => this.togglePanel());

    atom.tooltips.add(this.testerStatusBar, {
      title: 'failed | skipped | passed
Click to toggle the tester console output view.',
    });

    const statusBarTile = statusBar.addLeftTile({
      item: this.testerStatusBar,
      priority: -50,
    });

    statusBarTile.status = {};
    statusBarTile.status.passed = passedTestsSpan;
    statusBarTile.status.failed = failedTestsSpan;
    statusBarTile.status.skipped = skippedTestsSpan;
    statusBarTile.status.statusTiny = statusTiny;
    statusBarTile.testRunning = function testRunning() {
      self.runningTestersCount += 1;
      this.status.statusTiny.removeClass('idle');
    };
    statusBarTile.testFinished = function testFinished() {
      self.runningTestersCount -= 1;
      if (!(self.runningTestersCount > 0)) {
        this.status.statusTiny.addClass('idle');
      }
    };

    statusBarTile.resetResults = function resetResults(editor) {
      if (editor && editor.testerLastResults) {
        this.setResults(editor, editor.testerLastResults);
      } else {
        this.status.passed.text('0').removeClass('highlight-success');
        this.status.failed.text('0').removeClass('highlight-error');
        this.status.skipped.text('0').removeClass('highlight-warning');
      }
    };

    statusBarTile.setResults = function setResults(editor, results) {
      if (editor && results) {
        editor.testerLastResults = results;
      }
      if (results && editor !== atom.workspace.getActiveTextEditor() && self.showNotifications) {
        const passedTests = results.filter(result => result.state === 'passed').length;
        const failedTests = results.filter(result => result.state === 'failed').length;
        const skippedTests = results.filter(result => result.state === 'skipped').length;
        const resultMessage = `${failedTests} failed
                               ${skippedTests} skipped
                               ${passedTests} passed`;
        atom.notifications.addInfo(`<b>Tester:</b> Results for <b>${editor.getTitle()}</b><br/>${resultMessage}`, { icon: 'checklist' });
      }
      if (!results || editor !== atom.workspace.getActiveTextEditor()) {
        this.resetResults();
      } else {
        const passedTests = results.filter(result => result.state === 'passed').length;
        this.status.passed.text(passedTests);
        if (passedTests > 0) {
          this.status.passed.addClass('highlight-success');
        } else {
          this.status.passed.removeClass('highlight-success');
        }

        const failedTests = results.filter(result => result.state === 'failed').length;
        this.status.failed.text(failedTests);
        if (failedTests > 0) {
          this.status.failed.addClass('highlight-error');
        } else {
          this.status.failed.removeClass('highlight-error');
        }

        const skippedTests = results.filter(result => result.state === 'skipped').length;
        this.status.skipped.text(skippedTests);
        if (skippedTests > 0) {
          this.status.skipped.addClass('highlight-warning');
        } else {
          this.status.skipped.removeClass('highlight-warning');
        }
      }
    };
    this.statusBarTile = statusBarTile;
    this.statusBarTile.getItem().style.display = 'block';
  }

  async setOutput(editor : TextEditor, output :string) {
    if (this.panel) {
      await this.panel.update({ output });
    }
    // OutputViewManager.getView().setContent(output, this.ansiToHtml).finish(this.showOutputAfterTestRun, this.messageTimeout, this.scrollToBottom);
  }

  async togglePanel() {
    if (!this.panel) {
      this.panel = new OutputView({
        output: this.output,
        ansiToHtml: this.ansiToHtml,
        scrollToBottom: this.scrollToBottom });
    } else if (this.panel) {
      await this.panel.destroy();
      this.panel = null;
    }
  }

  dispose() {
    if (this.statusBarTile) {
      this.statusBarTile.destroy();
    }
    this.subscriptions.dispose();
  }

  addTester(tester : TesterProvider) {
    this.registryTesters.addTester(tester);
  }

  deleteTester(tester : TesterProvider) {
    this.registryTesters.deleteTester(tester);
  }
}

module.exports = Tester;
<|endoftext|>


<commit_before>var headless = require('headless');
var fs = require('fs');
var path = require('path');
var spawn = require('child_process').spawn;

var argue = {};
argue.firefox = function (br, uri, opts, cb) {
    var prefs = '';
    if (opts.proxy) {
        var m = /^(?:http:\/\/)?([^:\/]+)(?::(\d+))?/.exec(opts.proxy);
        var host = JSON.stringify(m[1]);
        var port = m[2] || 80;
        prefs +=
            'user_pref("network.proxy.http", ' + host + ');\n'
            + 'user_pref("network.proxy.http_port", ' + port + ');\n'
            + 'user_pref("network.proxy.type", 1);\n'
        ;
    }

    var file = path.join(path.dirname(br.profile.file), 'user.js');
    fs.writeFile(file, prefs, function (err) {
        if (err) cb(err)
        else cb(null, [
            '-no-remote',
            '-P', br.profile.name,
            uri,
        ].reduce(function (acc,x) { return acc.concat(x) }, []))
    });
};

argue.chrome = function (br, uri, opts, cb) {
    cb(null, [
        opts.proxy ? [ '--proxy-server', opts.proxy ] : [],
        br.profile ? [ '--user-data-dir', br.profile ] : [],
        uri,
    ].reduce(function (acc,x) { return acc.concat(x) }), []);
};

module.exports = function (config, name, version) {
    var m = selectMatch(config, name, version);
    if (!m) return;
    return function (uri, opts, cb) {
        if (opts.headless && m.headless) {
            headless(function (err, proc, display) {
                run({ DISPLAY : ':' + display });
            });
        }
        else run({})

        function run (env_) {
            var env = {};
            Object.keys(process.env).forEach(function (key) {
                env[key] = process.env[key];
            });
            Object.keys(env_).forEach(function (key) {
                env[key] = env_[key];
            });

            argue[m.type](m, uri, opts, function (err, args) {
                if (err) return cb(err)
                else cb(null, spawn(m.command, args, { env : env }))
            });
        }
    };
};

function selectMatch (config, name, version) {
    var order = (config.preference || []).concat(Object.keys(config.browsers));
    for (var i = 0; i < order.length; i++) {
        var bs = config.browsers[order[i]];
        var matching = bs
            .filter(function (b) {
                return b.name === name
                    && matches(b.version, version)
                ;
            })
            .sort(function (a, b) {
                return b.version - a.version;
            })
        ;
        if (matching.length) return matching[0];
    }
}

function matches (version, pattern) {
    if (pattern === undefined || pattern === '*') return true;

    // todo: real semvers
    var vs = version.split('.');
    var ps = pattern.split('.');
    for (var i = 0; i < ps.length; i++) {
        if (ps[i] === 'x' || ps[i] === '*') continue;
        if (ps[i] !== vs[i]) return false;
    }
    return true;
}
<commit_msg>fix headless<commit_after>var headless = require('headless');
var fs = require('fs');
var path = require('path');
var spawn = require('child_process').spawn;

var argue = {};
argue.firefox = function (br, uri, opts, cb) {
    var prefs = '';
    if (opts.proxy) {
        var m = /^(?:http:\/\/)?([^:\/]+)(?::(\d+))?/.exec(opts.proxy);
        var host = JSON.stringify(m[1]);
        var port = m[2] || 80;
        prefs +=
            'user_pref("network.proxy.http", ' + host + ');\n'
            + 'user_pref("network.proxy.http_port", ' + port + ');\n'
            + 'user_pref("network.proxy.type", 1);\n'
        ;
    }

    var file = path.join(path.dirname(br.profile.file), 'user.js');
    fs.writeFile(file, prefs, function (err) {
        if (err) cb(err)
        else cb(null, [
            '-no-remote',
            '-P', br.profile.name,
            uri,
        ].reduce(function (acc,x) { return acc.concat(x) }, []))
    });
};

argue.chrome = function (br, uri, opts, cb) {
    cb(null, [
        opts.proxy ? [ '--proxy-server', opts.proxy ] : [],
        br.profile ? [ '--user-data-dir', br.profile ] : [],
        uri,
    ].reduce(function (acc,x) { return acc.concat(x) }), []);
};

module.exports = function (config, name, version) {
    var m = selectMatch(config, name, version);
    if (!m) return;
    return function (uri, opts, cb) {
        if (opts.headless && !m.headless) {
            headless(function (err, proc, display) {
                run({ DISPLAY : ':' + display });
            });
        }
        else run({})

        function run (env_) {
            var env = {};
            Object.keys(process.env).forEach(function (key) {
                env[key] = process.env[key];
            });
            Object.keys(env_).forEach(function (key) {
                env[key] = env_[key];
            });

            argue[m.type](m, uri, opts, function (err, args) {
                if (err) return cb(err)
                else cb(null, spawn(m.command, args, { env : env }))
            });
        }
    };
};

function selectMatch (config, name, version) {
    var order = (config.preference || []).concat(Object.keys(config.browsers));
    for (var i = 0; i < order.length; i++) {
        var bs = config.browsers[order[i]];
        var matching = bs
            .filter(function (b) {
                return b.name === name
                    && matches(b.version, version)
                ;
            })
            .sort(function (a, b) {
                return b.version - a.version;
            })
        ;
        if (matching.length) return matching[0];
    }
}

function matches (version, pattern) {
    if (pattern === undefined || pattern === '*') return true;

    // todo: real semvers
    var vs = version.split('.');
    var ps = pattern.split('.');
    for (var i = 0; i < ps.length; i++) {
        if (ps[i] === 'x' || ps[i] === '*') continue;
        if (ps[i] !== vs[i]) return false;
    }
    return true;
}
<|endoftext|>


<commit_before>/*
 *  Charon: A game authentication server
 *  Copyright (C) 2014  Alex Mayfield
 *
 *  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/>.
 */

/* jshint node: true */
"use strict";

var winston = require('winston');

var Config = require('./config');

function Logger(config) {
	this.config = new Config(config, {
		file: undefined,
		verbose: false
	});

	this.logger = new (winston.Logger)({
		transports: [
			new (winston.transports.Console)({
				handleExceptions: true,
				level: this.config.getBool('verbose') ? 'verbose' : 'info'
			})
		]
	});

	this.log = this.logger.log;
	this.verbose = this.logger.verbose;
	this.info = this.logger.info;
	this.warn = this.logger.warn;
	this.error = this.logger.error;

	// Log to a file if supplied
	if (this.config.get('file')) {
		this.logger.add(winston.transports.File, {
			filename: config.log.file,
			handleExceptions: true,
			json: false,
			timestamp: true
		});
	}
}

module.exports = Logger;
<commit_msg>Fix instance of referring to 'config' object.<commit_after>/*
 *  Charon: A game authentication server
 *  Copyright (C) 2014  Alex Mayfield
 *
 *  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/>.
 */

/* jshint node: true */
"use strict";

var winston = require('winston');

var Config = require('./config');

function Logger(config) {
	this.config = new Config(config, {
		file: undefined,
		verbose: false
	});

	this.logger = new (winston.Logger)({
		transports: [
			new (winston.transports.Console)({
				handleExceptions: true,
				level: this.config.getBool('verbose') ? 'verbose' : 'info'
			})
		]
	});

	this.log = this.logger.log;
	this.verbose = this.logger.verbose;
	this.info = this.logger.info;
	this.warn = this.logger.warn;
	this.error = this.logger.error;

	// Log to a file if supplied
	if (this.config.get('file')) {
		this.logger.add(winston.transports.File, {
			filename: this.config.get('file'),
			handleExceptions: true,
			json: false,
			timestamp: true
		});
	}
}

module.exports = Logger;
<|endoftext|>


<commit_before>const {CompositeDisposable, Disposable} = require("atom")
const DefaultIgnoreCommands = [
  "cursor-history:next",
  "cursor-history:prev",
  "cursor-history:next-within-editor",
  "cursor-history:prev-within-editor",
  "cursor-history:clear",
]

function getClosestEditorForTarget(target) {
  if (!target || !target.closest("atom-text-editor")) return
  const editor = target.closest("atom-text-editor").getModel()
  // return editor
  return !editor.isMini() ? editor : undefined
}

function createLocation(editor, command) {
  return {
    command,
    editor,
    point: editor.getCursorBufferPosition(),
    URI: editor.getURI(),
  }
}

module.exports = {
  serialize() {
    return {
      history: this.history ? this.history.serialize() : this.restoredState.history,
    }
  },

  activate(restoredState) {
    this.restoredState = restoredState
    this.subscriptions = new CompositeDisposable()
    this.locationCheckTimeoutID = null

    const jump = (...args) => this.getHistory().jump(...args)

    this.subscriptions.add(
      atom.commands.add("atom-text-editor", {
        "cursor-history:next"() {
          jump(this.getModel(), "next")
        },
        "cursor-history:prev"() {
          jump(this.getModel(), "prev")
        },
        "cursor-history:next-within-editor"() {
          jump(this.getModel(), "next", {withinEditor: true})
        },
        "cursor-history:prev-within-editor"() {
          jump(this.getModel(), "prev", {withinEditor: true})
        },
        "cursor-history:dump-history": () => {
          this.getHistory().log("DUMP")
        },
        "cursor-history:clear": () => this.history && this.history.clear(),
        "cursor-history:toggle-debug": () => this.toggleDebug(),
      })
    )

    this.observeMouse()
    this.observeCommands()
    this.subscriptions.add(
      atom.config.observe("cursor-history.ignoreCommands", newValue => {
        this.ignoreCommands = DefaultIgnoreCommands.concat(newValue)
      })
    )
  },

  toggleDebug() {
    const newValue = !atom.config.get("cursor-history.debug")
    atom.config.set("cursor-history.debug", newValue)
    console.log("debug: ", newValue)
  },

  deactivate() {
    clearTimeout(this.locationCheckTimeoutID)
    this.subscriptions.dispose()
    if (this.history) this.history.destroy()
    this.subscriptions = this.history = this.locationCheckTimeoutID = null
  },

  getHistory() {
    if (!this.history) {
      History = require("./history")
      this.history = (this.restoredState ? this.restoredState.history : undefined)
        ? History.deserialize(this.restoredState.history)
        : new History()
    }

    return this.history
  },

  // When mouse clicked, cursor position is updated by atom core using setCursorScreenPosition()
  // To track cursor position change caused by mouse click, I use mousedown event.
  //  - Event capture phase: Cursor position is not yet changed.
  //  - Event bubbling phase: Cursor position updated to clicked position.
  observeMouse() {
    const locationStack = []

    const checkLocationChangeAfter = (location, timeout) => {
      clearTimeout(this.locationCheckTimeoutID)
      this.locationCheckTimeoutID = setTimeout(() => {
        this.checkLocationChange(location)
      }, timeout)
    }

    const handleCapture = function(event) {
      const editor = getClosestEditorForTarget(event.target)
      if (editor) {
        // In case, mousedown event was not **bubbled** up, detect location change
        // by comparing old and new location after 300ms
        // This task is cancelled when mouse event bubbled up to avoid duplicate
        // location check.
        //
        // E.g. hyperclick package open another file by mouseclick, it explicitly
        // call `event.stopPropagation()` to prevent default mouse behavior of Atom.
        // In such case we can't catch mouseclick event at bublling phase.
        const location = createLocation(editor, "mousedown")
        locationStack.push(location)
        checkLocationChangeAfter(location, 300)
      }
    }

    const handleBubble = event => {
      clearTimeout(this.locationCheckTimeoutID)
      const location = locationStack.pop()
      if (location) setTimeout(() => this.checkLocationChange(location), 100)
    }

    const workspaceElement = atom.views.getView(atom.workspace)
    workspaceElement.addEventListener("mousedown", handleCapture, true)
    workspaceElement.addEventListener("mousedown", handleBubble, false)

    this.subscriptions.add(
      new Disposable(function() {
        workspaceElement.removeEventListener("mousedown", handleCapture, true)
        workspaceElement.removeEventListener("mousedown", handleBubble, false)
      })
    )
  },

  observeCommands() {
    let trackThrottlingTimeout
    const locationStack = []
    this.locationStackForTestSpec = locationStack // expose to make test easy

    const isInterestingCommand = type => {
      return type.includes(":") && !this.ignoreCommands.includes(type)
    }

    const clearThrottling = function() {
      clearTimeout(trackThrottlingTimeout)
      trackThrottlingTimeout = null
    }

    const disposableForWillDispatch = atom.commands.onWillDispatch(({type, target}) => {
      if (!isInterestingCommand(type)) return
      const editor = getClosestEditorForTarget(target)
      if (editor) {
        if (!trackThrottlingTimeout) {
          locationStack.push(createLocation(editor, type))
        }
        trackThrottlingTimeout = setTimeout(clearThrottling, 100)
      }
    })

    const disposableForDidDispatch = atom.commands.onDidDispatch(({type, target}) => {
      if (!locationStack.length) return
      if (!isInterestingCommand(type)) return
      if (getClosestEditorForTarget(target)) {
        // To wait cursor position is set on final destination.
        setTimeout(() => {
          this.checkLocationChange(locationStack.pop())
        }, 100)
      }
    })

    this.subscriptions.add(disposableForWillDispatch, disposableForDidDispatch)
  },

  checkLocationChange(oldLocation) {
    if (oldLocation == null) return

    const editor = atom.workspace.getActiveTextEditor()
    if (!editor) return

    if (
      !editor.element.hasFocus() || oldLocation.URI
        ? editor.getURI() !== oldLocation.URI
        : editor !== oldLocation.editor
    ) {
      this.getHistory().add(oldLocation, {message: `Save on focus lost [${oldLocation.command}]`})
      return
    }

    // Move within same buffer.
    const oldPoint = oldLocation.point
    const newPoint = editor.getCursorBufferPosition()
    const {row, column} = oldPoint.isGreaterThan(newPoint)
      ? oldPoint.traversalFrom(newPoint)
      : newPoint.traversalFrom(oldPoint)

    if (
      row > atom.config.get("cursor-history.rowDeltaToRemember") ||
      (row === 0 && column > atom.config.get("cursor-history.columnDeltaToRemember"))
    ) {
      this.getHistory().add(oldLocation, {message: `Cursor moved [${oldLocation.command}]`})
    }
  },
}
<commit_msg>critical pre-released bug fix, mis-conversion from coffee-to-JS<commit_after>const {CompositeDisposable, Disposable} = require("atom")
const DefaultIgnoreCommands = [
  "cursor-history:next",
  "cursor-history:prev",
  "cursor-history:next-within-editor",
  "cursor-history:prev-within-editor",
  "cursor-history:clear",
]

function getClosestEditorForTarget(target) {
  if (!target || !target.closest("atom-text-editor")) return
  const editor = target.closest("atom-text-editor").getModel()
  // return editor
  return !editor.isMini() ? editor : undefined
}

function createLocation(editor, command) {
  return {
    command,
    editor,
    point: editor.getCursorBufferPosition(),
    URI: editor.getURI(),
  }
}

module.exports = {
  serialize() {
    return {
      history: this.history ? this.history.serialize() : this.restoredState.history,
    }
  },

  activate(restoredState) {
    this.restoredState = restoredState
    this.subscriptions = new CompositeDisposable()
    this.locationCheckTimeoutID = null

    const jump = (...args) => this.getHistory().jump(...args)

    this.subscriptions.add(
      atom.commands.add("atom-text-editor", {
        "cursor-history:next"() {
          jump(this.getModel(), "next")
        },
        "cursor-history:prev"() {
          jump(this.getModel(), "prev")
        },
        "cursor-history:next-within-editor"() {
          jump(this.getModel(), "next", {withinEditor: true})
        },
        "cursor-history:prev-within-editor"() {
          jump(this.getModel(), "prev", {withinEditor: true})
        },
        "cursor-history:dump-history": () => {
          this.getHistory().log("DUMP")
        },
        "cursor-history:clear": () => this.history && this.history.clear(),
        "cursor-history:toggle-debug": () => this.toggleDebug(),
      })
    )

    this.observeMouse()
    this.observeCommands()
    this.subscriptions.add(
      atom.config.observe("cursor-history.ignoreCommands", newValue => {
        this.ignoreCommands = DefaultIgnoreCommands.concat(newValue)
      })
    )
  },

  toggleDebug() {
    const newValue = !atom.config.get("cursor-history.debug")
    atom.config.set("cursor-history.debug", newValue)
    console.log("debug: ", newValue)
  },

  deactivate() {
    clearTimeout(this.locationCheckTimeoutID)
    this.subscriptions.dispose()
    if (this.history) this.history.destroy()
    this.subscriptions = this.history = this.locationCheckTimeoutID = null
  },

  getHistory() {
    if (!this.history) {
      History = require("./history")
      this.history = (this.restoredState ? this.restoredState.history : undefined)
        ? History.deserialize(this.restoredState.history)
        : new History()
    }

    return this.history
  },

  // When mouse clicked, cursor position is updated by atom core using setCursorScreenPosition()
  // To track cursor position change caused by mouse click, I use mousedown event.
  //  - Event capture phase: Cursor position is not yet changed.
  //  - Event bubbling phase: Cursor position updated to clicked position.
  observeMouse() {
    const locationStack = []

    const checkLocationChangeAfter = (location, timeout) => {
      clearTimeout(this.locationCheckTimeoutID)
      this.locationCheckTimeoutID = setTimeout(() => {
        this.checkLocationChange(location)
      }, timeout)
    }

    const handleCapture = function(event) {
      const editor = getClosestEditorForTarget(event.target)
      if (editor) {
        // In case, mousedown event was not **bubbled** up, detect location change
        // by comparing old and new location after 300ms
        // This task is cancelled when mouse event bubbled up to avoid duplicate
        // location check.
        //
        // E.g. hyperclick package open another file by mouseclick, it explicitly
        // call `event.stopPropagation()` to prevent default mouse behavior of Atom.
        // In such case we can't catch mouseclick event at bublling phase.
        const location = createLocation(editor, "mousedown")
        locationStack.push(location)
        checkLocationChangeAfter(location, 300)
      }
    }

    const handleBubble = event => {
      clearTimeout(this.locationCheckTimeoutID)
      const location = locationStack.pop()
      if (location) setTimeout(() => this.checkLocationChange(location), 100)
    }

    const workspaceElement = atom.views.getView(atom.workspace)
    workspaceElement.addEventListener("mousedown", handleCapture, true)
    workspaceElement.addEventListener("mousedown", handleBubble, false)

    this.subscriptions.add(
      new Disposable(function() {
        workspaceElement.removeEventListener("mousedown", handleCapture, true)
        workspaceElement.removeEventListener("mousedown", handleBubble, false)
      })
    )
  },

  observeCommands() {
    let trackThrottlingTimeout
    const locationStack = []
    this.locationStackForTestSpec = locationStack // expose to make test easy

    const isInterestingCommand = type => {
      return type.includes(":") && !this.ignoreCommands.includes(type)
    }

    const clearThrottling = function() {
      clearTimeout(trackThrottlingTimeout)
      trackThrottlingTimeout = null
    }

    const disposableForWillDispatch = atom.commands.onWillDispatch(({type, target}) => {
      if (!isInterestingCommand(type)) return
      const editor = getClosestEditorForTarget(target)
      if (editor) {
        if (!trackThrottlingTimeout) {
          locationStack.push(createLocation(editor, type))
        }
        trackThrottlingTimeout = setTimeout(clearThrottling, 100)
      }
    })

    const disposableForDidDispatch = atom.commands.onDidDispatch(({type, target}) => {
      if (!locationStack.length) return
      if (!isInterestingCommand(type)) return
      if (getClosestEditorForTarget(target)) {
        // To wait cursor position is set on final destination.
        setTimeout(() => {
          this.checkLocationChange(locationStack.pop())
        }, 100)
      }
    })

    this.subscriptions.add(disposableForWillDispatch, disposableForDidDispatch)
  },

  checkLocationChange(oldLocation) {
    if (oldLocation == null) return

    const editor = atom.workspace.getActiveTextEditor()
    if (!editor) return

    const {URI: oldURI, point: oldPoint, editor: oldEditor} = oldLocation
    if (!editor.element.hasFocus() || (oldURI ? editor.getURI() !== oldURI : editor !== oldEditor)) {
      this.getHistory().add(oldLocation, {message: `Save on focus lost [${oldLocation.command}]`})
      return
    }

    // Move within same buffer.
    const newPoint = editor.getCursorBufferPosition()
    const {row, column} = oldPoint.isGreaterThan(newPoint)
      ? oldPoint.traversalFrom(newPoint)
      : newPoint.traversalFrom(oldPoint)

    if (
      row > atom.config.get("cursor-history.rowDeltaToRemember") ||
      (row === 0 && column > atom.config.get("cursor-history.columnDeltaToRemember"))
    ) {
      this.getHistory().add(oldLocation, {message: `Cursor moved [${oldLocation.command}]`})
    }
  },
}
<|endoftext|>


<commit_before>/**
 * Decipher a signature based on action tokens.
 *
 * @param {Array.<String>} tokens
 * @param {String} sig
 * @return {String}
 */
exports.decipher = function(tokens, sig) {
  console.log('orig');
  console.log(sig);
  sig = sig.split('');
  var pos, d;
  for (var i = 0, len = tokens.length; i < len; i++) {
    var token = tokens[i];
    switch (token[0]) {
    case 'r':
      sig = sig.reverse();
      console.log('reverse');
      console.log(sig.join(''));
      break;
    case 'w':
      pos = ~~token.slice(1);
      sig = swapHeadAndPosition(sig, pos);
      console.log('swap', pos);
      d = sig.join('');
      console.log(d[0].cyan + d.slice(1, pos) +
        d[pos].cyan + d.slice(pos + 1));
      break;
    case 's':
      pos = ~~token.slice(1);
      var l = sig.slice(0, pos).join('');
      sig = sig.slice(pos);
      console.log('slice', pos);
      d = sig.join('');
      console.log(l.red + d);
    }
  }
  console.log();
  return sig.join('');
};

/**
 * Swaps the first element of an array with one of given position.
 *
 * @param {Array.<Object>} arr
 * @param {Number} position
 * @return {Array.<Object>}
 */
function swapHeadAndPosition(arr, position) {
  var first = arr[0];
  arr[0] = arr[position % arr.length];
  arr[position] = first;
  return arr;
}

/**
 * Extracts the actions that should be taken to decypher a signature.
 *
 * @param {String} body
 * @return {Array.<String>}
 */
/*jshint maxlen:false */
var actionsRegexp = /([a-z])=(?:\1|[a-z]\.[a-z])\.split\(""\)(;|,)((?:\1=(?:\1\.reverse\(\)|[a-zA-Z]{2}\(\1,\d+\)|\1\.slice\(\d+\))\2)+)(?:return |\1=)\1\.join\(""\)/;
exports.extractActions = function(body) {
  var result = actionsRegexp.exec(body);
  if (!result) { return null; }

  console.log('RESULTS', result[0]);
  var sig = result[1];
  var sep = result[2];
  var acts = result[3];
  var myreg = sig + '=(?:' +
      '(' + sig + '\\.reverse\\(\\))|' +
      '([a-zA-Z]{2}\\(' + sig + ',(\\d+)\\))|' +
      '(' + sig + '\\.slice\\((\\d+)\\))' +
    ')' + sep;
  var tokenizeRegexp = new RegExp(myreg, 'g');
  var tokens = [];
  while ((result = tokenizeRegexp.exec(acts)) !== null) {
    if (result[1]) {
      tokens.push('r');
      console.log('push reverse');
    } else if (result[2]) {
      tokens.push('w' + result[3]);
      console.log('push swap', result[3]);
    } else if (result[4]) {
      tokens.push('s' + result[5]);
      console.log('push slice', result[5]);
    }
  }
  return tokens;
};
<commit_msg>more logging<commit_after>/**
 * Decipher a signature based on action tokens.
 *
 * @param {Array.<String>} tokens
 * @param {String} sig
 * @return {String}
 */
exports.decipher = function(tokens, sig) {
  console.log('orig');
  console.log(sig);
  console.log(sig.length);
  sig = sig.split('');
  var pos, d;
  for (var i = 0, len = tokens.length; i < len; i++) {
    var token = tokens[i];
    switch (token[0]) {
    case 'r':
      sig = sig.reverse();
      console.log('reverse');
      console.log(sig.join(''));
      console.log(sig.length);
      break;
    case 'w':
      pos = ~~token.slice(1);
      sig = swapHeadAndPosition(sig, pos);
      console.log('swap', pos);
      d = sig.join('');
      console.log(d[0].cyan + d.slice(1, pos) +
        d[pos].cyan + d.slice(pos + 1));
      console.log(sig.length);
      break;
    case 's':
      pos = ~~token.slice(1);
      var l = sig.slice(0, pos).join('');
      sig = sig.slice(pos);
      console.log('slice', pos);
      d = sig.join('');
      console.log(l.red + d);
      console.log(sig.length);
    }
  }
  console.log();
  return sig.join('');
};

/**
 * Swaps the first element of an array with one of given position.
 *
 * @param {Array.<Object>} arr
 * @param {Number} position
 * @return {Array.<Object>}
 */
function swapHeadAndPosition(arr, position) {
  var first = arr[0];
  arr[0] = arr[position % arr.length];
  arr[position] = first;
  return arr;
}

/**
 * Extracts the actions that should be taken to decypher a signature.
 *
 * @param {String} body
 * @return {Array.<String>}
 */
var actionsRegexp = new RegExp(
  '([a-z])=(?:\\1|[a-z]\\.[a-z])\\.split\\(""\\)(;|,)' +
  '((?:\\1=(?:' +
    '\\1\\.reverse\\(\\)|' +
    '[a-zA-Z]{2}\\(\\1,\\d+\\)|' +
    '\\1\\.slice\\(\\d+\\)' +
  ')\\2)+)' +
  '(?:return |\\1=)\\1\\.join\\(""\\)'
);
exports.extractActions = function(body) {
  var result = actionsRegexp.exec(body);
  if (!result) { return null; }

  console.log('RESULTS', result[0]);
  var sig = result[1];
  var sep = result[2];
  var acts = result[3];
  var myreg = sig + '=(?:' +
      '(' + sig + '\\.reverse\\(\\))|' +
      '([a-zA-Z]{2}\\(' + sig + ',(\\d+)\\))|' +
      '(' + sig + '\\.slice\\((\\d+)\\))' +
    ')' + sep;
  var tokenizeRegexp = new RegExp(myreg, 'g');
  var tokens = [];
  while ((result = tokenizeRegexp.exec(acts)) !== null) {
    if (result[1]) {
      tokens.push('r');
      console.log('push reverse');
    } else if (result[2]) {
      tokens.push('w' + result[3]);
      console.log('push swap', result[3]);
    } else if (result[4]) {
      tokens.push('s' + result[5]);
      console.log('push slice', result[5]);
    }
  }
  return tokens;
};
<|endoftext|>


<commit_before>'use strict';

var express = require('express'),
    router = express.Router(),
    User = require('../models/user'),
    graph = require('fbgraph'),
    config;

//base: /users

router.post('/auth', function (req, res) {
    var fbUserId = req.body.fbId,
        fbAccessToken = req.body.fbAccessToken,
        accessToken = req.body.accessToken;

    console.log('params', fbUserId, fbAccessToken, accessToken);

    if (!fbUserId || (!fbAccessToken && !accessToken)) {
        return res.status(500).send("Not enough data!");
    }

    if (accessToken) {
        User.getByAccessToken(accessToken, function (err, user) {
            if (err) {
                //user does not exist
                return res.json(500, err);
            }

            if (!user) {
                console.log('Invalid Access Token!', accessToken);
                return res.status(500).json('Invalid Access Token!');
            }
        });
    } else {
        console.log('graph: set appsecret to: ' + config.facebook.app.secret);
        graph.setAppSecret(config.facebook.app.secret);
        graph.setAccessToken(fbAccessToken);

        graph.get('me?fields=id,name,picture', function (err, graphUser) {
            if (err) {
                return res.status(400).json(err);
            }

            if (graphUser.id !== fbUserId) {
                return res.json(400, {error: "User ID and AccessToken don't match!"});
            }

            console.log('/me user from token:', graphUser);

            User.fb(fbUserId, function (err, user) {
                if (err) {
                    return res.json(400, err);
                }

                if (!user) {
                    User.create({
                        facebookId: graphUser.id,
                        name: graphUser.name,
                        photo: graphUser.picture.data.url
                    }, function (err, user) {
                        if (err) {
                            return res.json(400, err);
                        }

                        if (!user) {
                            return res.json(400, {error: 'Could not create new user!'});
                        }

                        return res.json(200, {accessToken: user.accessToken});
                    });
                }

                return res.json(200, {accessToken: user.accessToken});
            });

        });
    }
});

module.exports = exports = router;

module.exports.setAppConfig = exports.setAppConfig = function (cnf) {
    config = cnf;
};
<commit_msg>more console debug for API create/get user on login<commit_after>'use strict';

var express = require('express'),
    router = express.Router(),
    User = require('../models/user'),
    graph = require('fbgraph'),
    config;

//base: /users

router.post('/auth', function (req, res) {
    var fbUserId = req.body.fbId,
        fbAccessToken = req.body.fbAccessToken,
        accessToken = req.body.accessToken;

    console.log('params', fbUserId, fbAccessToken, accessToken);

    if (!fbUserId || (!fbAccessToken && !accessToken)) {
        return res.status(500).send("Not enough data!");
    }

    if (accessToken) {
        User.getByAccessToken(accessToken, function (err, user) {
            if (err) {
                //user does not exist
                return res.json(500, err);
            }

            if (!user) {
                console.log('Invalid Access Token!', accessToken);
                return res.status(500).json('Invalid Access Token!');
            }
        });
    } else {
        console.log('graph: set appsecret to: ' + config.facebook.app.secret);
        graph.setAppSecret(config.facebook.app.secret);
        graph.setAccessToken(fbAccessToken);

        graph.get('me?fields=id,name,picture', function (err, graphUser) {
            if (err) {
                return res.status(400).json(err);
            }

            if (graphUser.id !== fbUserId) {
                return res.json(400, {error: "User ID and AccessToken don't match!"});
            }

            console.log('/me user from token:', graphUser);

            User.fb(fbUserId, function (err, user) {
                if (err) {
                    console.log('API: error searching for user with User.fb('+fbUserId+')', err, user);
                    return res.json(400, err);
                }

                if (!user) {
                    //TODO: use var obj = {...} to avoid repeated code for log
                    console.log('API: creating new user', {
                        facebookId: graphUser.id,
                        name: graphUser.name,
                        photo: graphUser.picture.data.url
                    });
                    User.create({
                        facebookId: graphUser.id,
                        name: graphUser.name,
                        photo: graphUser.picture.data.url
                    }, function (err, user) {
                        if (err) {
                            console.log('API: error creating new user, err = ', err);
                            return res.json(400, err);
                        }

                        if (!user) {
                            console.log('API: error creating new user, user = ', user);
                            return res.json(400, {error: 'Could not create new user!'});
                        }

                        return res.json(200, {accessToken: user.accessToken});
                    });
                }

                return res.json(200, {accessToken: user.accessToken});
            });

        });
    }
});

module.exports = exports = router;

module.exports.setAppConfig = exports.setAppConfig = function (cnf) {
    config = cnf;
};
<|endoftext|>


<commit_before>"use strict";

import isValidPackageName from "validate-npm-package-name";
import XRegExp from "xregexp";

export default class Substituter {

    replaceAllCommands(text) {
        text = this.replaceInstallCommands(text);
        text = this.replaceConfigCommands(text);
        text = this.replaceLinkCommands(text);
        text = this.replaceRebuildCommands(text);
        text = this.replaceCacheCleanCommands(text);
        return text;
    }

    validatePackageName(packageName) {
        let splitPackageName = packageName.split("@")[0]
        let packageNameWithoutVersion = splitPackageName[0] == '' ? '@'+splitPackageName[1] : splitPackageName[0];

        if (!isValidPackageName(packageNameWithoutVersion).validForOldPackages) {
            throw new Error(`${packageName} is not a valid package name`);
        }
    }

    replaceInstallCommands(text) {
        const mappings = {
            "add": {
                "regex": /i(?!nit)(?:nstall)?/,
                "supportsGlobal": true,
                "minParameters": 1,
                "parameterValidator": this.validatePackageName
            },
            "remove": {
                "regex": /(?:uninstall|remove|rm|r|un|unlink)/,
                "supportsGlobal": true,
                "minParameters": 1,
                "parameterValidator": this.validatePackageName
            }
        }

        const flagMappings = {
            "": ["-S", "--save"],
            "--dev": ["-D", "--save-dev"],
            "--optional": ["-O", "--save-optional"],
            "--exact": ["-E", "--save-exact"],
        }

        text = this.replaceUsingMappings(text, mappings, flagMappings, true);
        text = this.replaceUsingMappings(text, {
            "install": {
                "regex": /i(?!nit)(?:nstall)?/,
                "maxParameters": 0
            }
        });

        return text;
    }

    replaceConfigCommands(text) {
        // TODO: Find out if these should support global or not
        // Docs (https://yarnpkg.com/en/docs/cli/config) say yes, CLI says no
        const mappings = {
            "config set": {
                "regex": /(?:c(?:onfig)?[^\S\n\r])?set/,
                "minParameters": 2
            },
            "config get": {
                "regex": /(?:c(?:onfig)?[^\S\n\r])?get/,
                "minParameters": 1,
                "maxParameters": 1
            },
            "config delete": {
                "regex": /c(?:onfig)?[^\S\n\r]delete/,
                "minParameters": 1,
                "maxParameters": 1
            },
            "config list": {
                "regex": /c(?:onfig)?[^\S\n\r]list/,
                "maxParameters": 0
            }
        }

        return this.replaceUsingMappings(text, mappings);
    }

    replaceLinkCommands(text) {
        const mappings = {
            "link": {
                "regex": /(?:link|ln)/,
                "parameterValidator": this.validatePackageName
            }
        }

        return this.replaceUsingMappings(text, mappings);
    }

    replaceRebuildCommands(text) {
        const mappings = {
            "install --force": {
                "regex": /rebuild/,
                "parameterValidator": this.validatePackageName
            }
        }

        return this.replaceUsingMappings(text, mappings);
    }

    replaceCacheCleanCommands(text) {
        const mappings = {
            "cache clean": {
                "regex": /cache[^\S\n\r]clean/,
                "parameterValidator": this.validatePackageName
            }
        }

        return this.replaceUsingMappings(text, mappings);
    }

    replaceUsingMappings(text, commandMappings, flagMappings = {}) {
        for (let yarnCommandName in commandMappings) {
            const { regex: npmRegex, supportsGlobal = false, maxParameters = -1, minParameters = 0, parameterValidator = null } = commandMappings[yarnCommandName];
            const supportsParameters = maxParameters != 0

            let regexString = "";

            regexString += `(npm\[^\\S\\n\\r]${npmRegex.source})`;
            if (supportsParameters) {
                regexString += /((?:(?:[^\S\n\r][^\n|\r|\.|"|']+))*)/.source;
            }

            const regex = new RegExp(regexString, "g");

            XRegExp.forEach(text, regex, (match) => {
                let flags = [];
                let parameters = [];
                let isGlobal = false;
                let parsedCommand = match[1];
                const args = (match.length > 2) ? match[2].match(/\s\S+/g) || [] : [];

                argumentLoop:
                for (let argument of args) {
                    const trimmedArgument = argument.trim();
                    if (trimmedArgument === "-g" || trimmedArgument === "--global") {
                        if (supportsGlobal) {
                            isGlobal = true;
                        } else {
                            throw new Error("A Global flag was found in a command that doesn't have a global equivalent");
                        }
                        parsedCommand += argument;
                        continue;
                    }

                    for (let yarnFlag in flagMappings) {
                        const npmFlags = flagMappings[yarnFlag];

                        if (npmFlags.includes(trimmedArgument)) {
                            if (yarnFlag !== "") {
                                flags.push(yarnFlag);
                            }
                            parsedCommand += argument;
                            continue argumentLoop;
                        }
                    }

                    if (supportsParameters) {
                        if (parameterValidator !== null) {
                            try {
                                parameterValidator(trimmedArgument);
                            } catch(error) {
                                break argumentLoop;
                            }
                        }

                        parameters.push(trimmedArgument);
                        parsedCommand += argument;
                        continue;
                    }

                    // End the for-loop on the first invalid argument
                    break;
                }

                if (parameters.length < minParameters) {
                    return;
                }

                let yarnCommand = "yarn";

                if (isGlobal) {
                    yarnCommand += " global";
                }

                yarnCommand += " " + yarnCommandName;

                const flagsString = flags.reduce((previousValue, currentValue) => `${previousValue} ${currentValue}`, "");
                const parametersString = parameters.reduce((previousValue, currentValue) => `${previousValue} ${currentValue}`, "");

                yarnCommand += flagsString + parametersString;

                const parsedCommandRegex = new RegExp(`${XRegExp.escape(parsedCommand)}`);
                text = text.replace(parsedCommandRegex, yarnCommand);
            });
        }

        return text;
    }

}
<commit_msg>Fix typo<commit_after>"use strict";

import isValidPackageName from "validate-npm-package-name";
import XRegExp from "xregexp";

export default class Substituter {

    replaceAllCommands(text) {
        text = this.replaceInstallCommands(text);
        text = this.replaceConfigCommands(text);
        text = this.replaceLinkCommands(text);
        text = this.replaceRebuildCommands(text);
        text = this.replaceCacheCleanCommands(text);
        return text;
    }

    validatePackageName(packageName) {
        let splitPackageName = packageName.split("@")
        let packageNameWithoutVersion = splitPackageName[0] == '' ? '@'+splitPackageName[1] : splitPackageName[0];

        if (!isValidPackageName(packageNameWithoutVersion).validForOldPackages) {
            throw new Error(`${packageName} is not a valid package name`);
        }
    }

    replaceInstallCommands(text) {
        const mappings = {
            "add": {
                "regex": /i(?!nit)(?:nstall)?/,
                "supportsGlobal": true,
                "minParameters": 1,
                "parameterValidator": this.validatePackageName
            },
            "remove": {
                "regex": /(?:uninstall|remove|rm|r|un|unlink)/,
                "supportsGlobal": true,
                "minParameters": 1,
                "parameterValidator": this.validatePackageName
            }
        }

        const flagMappings = {
            "": ["-S", "--save"],
            "--dev": ["-D", "--save-dev"],
            "--optional": ["-O", "--save-optional"],
            "--exact": ["-E", "--save-exact"],
        }

        text = this.replaceUsingMappings(text, mappings, flagMappings, true);
        text = this.replaceUsingMappings(text, {
            "install": {
                "regex": /i(?!nit)(?:nstall)?/,
                "maxParameters": 0
            }
        });

        return text;
    }

    replaceConfigCommands(text) {
        // TODO: Find out if these should support global or not
        // Docs (https://yarnpkg.com/en/docs/cli/config) say yes, CLI says no
        const mappings = {
            "config set": {
                "regex": /(?:c(?:onfig)?[^\S\n\r])?set/,
                "minParameters": 2
            },
            "config get": {
                "regex": /(?:c(?:onfig)?[^\S\n\r])?get/,
                "minParameters": 1,
                "maxParameters": 1
            },
            "config delete": {
                "regex": /c(?:onfig)?[^\S\n\r]delete/,
                "minParameters": 1,
                "maxParameters": 1
            },
            "config list": {
                "regex": /c(?:onfig)?[^\S\n\r]list/,
                "maxParameters": 0
            }
        }

        return this.replaceUsingMappings(text, mappings);
    }

    replaceLinkCommands(text) {
        const mappings = {
            "link": {
                "regex": /(?:link|ln)/,
                "parameterValidator": this.validatePackageName
            }
        }

        return this.replaceUsingMappings(text, mappings);
    }

    replaceRebuildCommands(text) {
        const mappings = {
            "install --force": {
                "regex": /rebuild/,
                "parameterValidator": this.validatePackageName
            }
        }

        return this.replaceUsingMappings(text, mappings);
    }

    replaceCacheCleanCommands(text) {
        const mappings = {
            "cache clean": {
                "regex": /cache[^\S\n\r]clean/,
                "parameterValidator": this.validatePackageName
            }
        }

        return this.replaceUsingMappings(text, mappings);
    }

    replaceUsingMappings(text, commandMappings, flagMappings = {}) {
        for (let yarnCommandName in commandMappings) {
            const { regex: npmRegex, supportsGlobal = false, maxParameters = -1, minParameters = 0, parameterValidator = null } = commandMappings[yarnCommandName];
            const supportsParameters = maxParameters != 0

            let regexString = "";

            regexString += `(npm\[^\\S\\n\\r]${npmRegex.source})`;
            if (supportsParameters) {
                regexString += /((?:(?:[^\S\n\r][^\n|\r|\.|"|']+))*)/.source;
            }

            const regex = new RegExp(regexString, "g");

            XRegExp.forEach(text, regex, (match) => {
                let flags = [];
                let parameters = [];
                let isGlobal = false;
                let parsedCommand = match[1];
                const args = (match.length > 2) ? match[2].match(/\s\S+/g) || [] : [];

                argumentLoop:
                for (let argument of args) {
                    const trimmedArgument = argument.trim();
                    if (trimmedArgument === "-g" || trimmedArgument === "--global") {
                        if (supportsGlobal) {
                            isGlobal = true;
                        } else {
                            throw new Error("A Global flag was found in a command that doesn't have a global equivalent");
                        }
                        parsedCommand += argument;
                        continue;
                    }

                    for (let yarnFlag in flagMappings) {
                        const npmFlags = flagMappings[yarnFlag];

                        if (npmFlags.includes(trimmedArgument)) {
                            if (yarnFlag !== "") {
                                flags.push(yarnFlag);
                            }
                            parsedCommand += argument;
                            continue argumentLoop;
                        }
                    }

                    if (supportsParameters) {
                        if (parameterValidator !== null) {
                            try {
                                parameterValidator(trimmedArgument);
                            } catch(error) {
                                break argumentLoop;
                            }
                        }

                        parameters.push(trimmedArgument);
                        parsedCommand += argument;
                        continue;
                    }

                    // End the for-loop on the first invalid argument
                    break;
                }

                if (parameters.length < minParameters) {
                    return;
                }

                let yarnCommand = "yarn";

                if (isGlobal) {
                    yarnCommand += " global";
                }

                yarnCommand += " " + yarnCommandName;

                const flagsString = flags.reduce((previousValue, currentValue) => `${previousValue} ${currentValue}`, "");
                const parametersString = parameters.reduce((previousValue, currentValue) => `${previousValue} ${currentValue}`, "");

                yarnCommand += flagsString + parametersString;

                const parsedCommandRegex = new RegExp(`${XRegExp.escape(parsedCommand)}`);
                text = text.replace(parsedCommandRegex, yarnCommand);
            });
        }

        return text;
    }

}
<|endoftext|>


<commit_before>'use strict';

const FS = require('./../lib/fs');

const DISHES_FILE = 'config/chat-plugins/thecafe-foodfight.json';
const FOODFIGHT_COOLDOWN = 5 * 60 * 1000;

const thecafe = /** @type {ChatRoom} */ (Rooms.get('thecafe'));

/** @type {{[k: string]: string[]}} */
let dishes = {};
try {
	dishes = require(`../${DISHES_FILE}`);
} catch (e) {
	if (e.code !== 'MODULE_NOT_FOUND' && e.code !== 'ENOENT') throw e;
}
if (!dishes || typeof dishes !== 'object') dishes = {};

function saveDishes() {
	FS(DISHES_FILE).write(JSON.stringify(dishes));
}

/**
 * Used instead of Dex.packTeam to generate more human-readable output.
 * @param {AnyObject[]} team
 * @param {string[]} ingredients
 */
function stringifyTeam(team, ingredients) {
	let output = '';
	for (let i = 0; i < team.length; i++) {
		const mon = team[i];
		output += `${ingredients[i]} (${mon.species}) @ ${mon.item}<br/>`;
		output += `Ability: ${mon.ability}<br/>`;
		if (mon.happiness !== 255) output += `Happiness: ${mon.happiness}<br/>`;
		let evs = [];
		for (const stat in mon.evs) {
			if (mon.evs[stat]) evs.push(`${mon.evs[stat]} ${stat}`);
		}
		if (evs.length) output += `EVs: ${evs.join(' / ')}<br/>`;
		output += `${mon.nature} Nature<br/>`;
		let ivs = [];
		for (const stat in mon.ivs) {
			if (mon.ivs[stat] !== 31) ivs.push(`${mon.ivs[stat]} ${stat}`);
		}
		if (ivs.length) output += `IVs: ${ivs.join(' / ')}<br/>`;
		output += /** @type {string[]} */ (mon.moves).map(move => `- ${move}<br/>`).join('');
		output += '
';
	}
	return output;
}

/**
 * @param {string} [generator]
 */
function generateTeam(generator = '') {
	let potentialPokemon = Object.keys(Dex.data.Pokedex).filter(mon => {
		const template = Dex.getTemplate(mon);
		return template.baseSpecies === template.species;
	});
	let speciesClause = true;
	switch (generator) {
	case 'ou':
		potentialPokemon = potentialPokemon.filter(mon => {
			const template = Dex.getTemplate(mon);
			return template.tier === 'OU';
		}).concat(potentialPokemon.filter(mon => {
			// There is probably a better way to get the ratios right, oh well.
			const template = Dex.getTemplate(mon);
			return template.tier === 'OU' || template.tier === 'UU';
		}));
		break;
	case 'ag':
		potentialPokemon = potentialPokemon.filter(mon => {
			const template = Dex.getTemplate(mon);
			const unviable = template.tier === 'NFE' || template.tier === 'PU' || template.tier === '(PU)' || template.tier.startsWith("LC");
			const illegal = template.tier === 'Unreleased' || template.tier === 'Illegal' || template.tier.startsWith("CAP");
			return !(unviable || illegal);
		});
		speciesClause = false;
		break;
	default:
		potentialPokemon = potentialPokemon.filter(mon => {
			const template = Dex.getTemplate(mon);
			const op = template.tier === 'AG' || template.tier === 'Uber';
			const unviable = template.tier === 'Illegal' || template.tier.includes("LC");
			return !(op || unviable);
		});
		potentialPokemon.push('miltank', 'miltank', 'miltank', 'miltank'); // 5x chance for miltank for flavor purposes.
	}

	/** @type {string[]} */
	const team = [];

	while (team.length < 6) {
		const randIndex = Math.floor(Math.random() * potentialPokemon.length);
		const potentialMon = potentialPokemon[randIndex];
		if (team.includes(potentialMon)) continue;
		team.push(potentialMon);
		if (speciesClause) potentialPokemon.splice(randIndex, 1);
	}

	return team.map(mon => Dex.getTemplate(mon).species);
}

/**
 * @return {[string, string[]]}
 */
function generateDish() {
	const keys = Object.keys(dishes);
	const entry = dishes[keys[Math.floor(Math.random() * keys.length)]].slice();
	const dish = entry.splice(0, 1)[0];
	const ingredients = [];
	while (ingredients.length < 6) {
		ingredients.push(entry.splice(Math.floor(Math.random() * entry.length), 1)[0]);
	}
	return [dish, ingredients];
}

/** @type {ChatCommands} */
const commands = {
	foodfight: function (target, room, user) {
		if (room !== thecafe) return this.errorReply("This command is only available in The Café.");

		if (!Object.keys(dishes).length) return this.errorReply("No dishes found. Add some dishes first.");

		// @ts-ignore
		if (user.foodfight && user.foodfight.timestamp + FOODFIGHT_COOLDOWN > Date.now()) return this.errorReply("Please wait a few minutes before using this command again.");

		target = toId(target);

		let team, importable;
		const [dish, ingredients] = generateDish();
		if (target === 'bf') {
			const bfTeam = Dex.generateTeam('gen7battlefactory');
			importable = stringifyTeam(bfTeam, ingredients);
			team = /** @type {Template[]} */ (bfTeam).map(val => val.species);
		} else {
			team = generateTeam(target);
		}
		// @ts-ignore
		user.foodfight = {team: team, dish: dish, ingredients: ingredients, timestamp: Date.now()};
		const importStr = importable ? `
Importable team:
${importable}</textarea>
` : '';
		return this.sendReplyBox(`<div class="ladder"><table style="text-align:center;"><tr><th colspan="7" style="font-size:10pt;">Your dish is: <u>${dish}</u></th></tr><tr><th>Team</th>${team.map(mon => `<psicon pokemon="${mon}"/> ${mon}	`).join('')}</tr><tr><th>Ingredients</th>${ingredients.map(ingredient => `${ingredient}	`).join('')}</tr>${importStr}</table></div>`);
	},
	checkfoodfight: function (target, room, user) {
		if (room !== thecafe) return this.errorReply("This command is only available in The Café.");

		const targetUser = this.targetUserOrSelf(target, false);
		if (!targetUser) return this.errorReply(`User ${this.targetUsername} not found.`);
		const self = targetUser === user;
		if (!self && !this.can('mute', targetUser, room)) return false;
		// @ts-ignore
		if (!targetUser.foodfight) return this.errorReply(`${self ? `You don't` : `This user doesn't`} have an active Foodfight team.`);
		// @ts-ignore
		return this.sendReplyBox(`<div class="ladder"><table style="text-align:center;"><tr><th colspan="7" style="font-size:10pt;">${self ? `Your` : `${this.targetUsername}'s`} dish is: <u>${targetUser.foodfight.dish}</u></th></tr><tr><th>Team</th>${targetUser.foodfight.team.map(mon => `<psicon pokemon="${mon}"/> ${mon}	`).join('')}</tr><tr><th>Ingredients</th>${targetUser.foodfight.ingredients.map(ingredient => `${ingredient}	`).join('')}</tr></table></div>`);
	},
	addingredients: 'adddish',
	adddish: function (target, room, user, connection, cmd) {
		if (room !== thecafe) return this.errorReply("This command is only available in The Café.");
		if (!this.can('mute', null, room)) return false;

		let [dish, ...ingredients] = target.split(',');
		dish = dish.trim();
		if (!dish || !ingredients.length) return this.parse('/help foodfight');
		const id = toId(dish);
		if (id === 'constructor') return this.errorReply("Invalid dish name.");
		ingredients = ingredients.map(ingredient => ingredient.trim());

		if ([...ingredients.entries()].some(([index, ingredient]) => ingredients.indexOf(ingredient) !== index)) {
			return this.errorReply("Please don't enter duplicate ingredients.");
		}

		if (ingredients.some(ingredient => ingredient.length > 19)) {
			return this.errorReply("Ingredients can only be 19 characters long.");
		}

		if (cmd === 'adddish') {
			if (dishes[id]) return this.errorReply("This dish already exists.");
			if (ingredients.length < 6) return this.errorReply("Dishes need at least 6 ingredients.");
			dishes[id] = [dish];
		} else {
			if (!dishes[id]) return this.errorReply(`Dish not found: ${dish}`);
			if (ingredients.some(ingredient => dishes[id].includes(ingredient))) return this.errorReply("Please don't enter duplicate ingredients.");
		}

		dishes[id] = dishes[id].concat(ingredients);
		saveDishes();
		this.sendReply(`${cmd.slice(3)} '${dish}: ${ingredients.join(', ')}' added successfully.`);
	},
	removedish: function (target, room, user) {
		if (room !== thecafe) return this.errorReply("This command is only available in The Café.");
		if (!this.can('mute', null, room)) return false;

		const id = toId(target);
		if (id === 'constructor') return this.errorReply("Invalid dish.");
		if (!dishes[id]) return this.errorReply(`Dish '${target}' not found.`);

		delete dishes[id];
		saveDishes();
		this.sendReply(`Dish '${target}' deleted successfully.`);
	},
	viewdishes: function (target, room, user, connection) {
		if (room !== thecafe) return this.errorReply("This command is only available in The Café.");

		return this.parse(`/join view-foodfight`);
	},
	foodfighthelp: [
		`/foodfight <generator> - Gives you a randomly generated Foodfight dish, ingredient list and team. Generator can be either 'ou' or 'ag', or left blank. If left blank, uses the normal Foodfight generator.`,
		`/checkfoodfight <username> - Gives you the last team and dish generated for the entered user, or your own if left blank. Anyone can check their own info, checking other people requires: % @ * # & ~`,
		`/adddish <dish>, <ingredient>, <ingredient>, ... - Adds a dish to the database. Requires: % @ * # & ~`,
		`/addingredients <dish>, <ingredient>, <ingredient>, ... - Adds extra ingredients to a dish in the database. Requires: % @ * # & ~`,
		`/removedish <dish> - Removes a dish from the database. Requires: % @ * # & ~`,
		`/viewdishes - Shows the entire database of dishes. Requires: % @ * # & ~`,
	],
};

exports.commands = commands;

/** @type {PageTable} */
const pages = {
	foodfight(query, user, connection) {
		if (!user.named) return Rooms.RETRY_AFTER_LOGIN;
		let buf = `|title|Foodfight\n|pagehtml|<div class="pad ladder"><h2>Foodfight Dish list</h2>`;
		if (!user.can('mute', null, thecafe)) {
			return buf + `<p>Access denied</p></div>`;
		}
		let content = Object.keys(dishes).map(entry => {
			const [dish, ...ingredients] = dishes[entry];
			return `<tr><td>${dish}</td><td>${ingredients.join(', ')}</td></tr>`;
		}).join('');

		if (!content) {
			buf += `<p>There are no dishes in the database.</p>`;
		} else {
			buf += `<table><tr><th><h3>Dishes</h3></th><th><h3>Ingredients</h3></th></tr>${content}</table>`;
		}
		buf += `</div>`;
		return buf;
	},
};
exports.pages = pages;
<commit_msg>Cafe: use BSS factory instead of normal BF to improve balance<commit_after>'use strict';

const FS = require('./../lib/fs');

const DISHES_FILE = 'config/chat-plugins/thecafe-foodfight.json';
const FOODFIGHT_COOLDOWN = 5 * 60 * 1000;

const thecafe = /** @type {ChatRoom} */ (Rooms.get('thecafe'));

/** @type {{[k: string]: string[]}} */
let dishes = {};
try {
	dishes = require(`../${DISHES_FILE}`);
} catch (e) {
	if (e.code !== 'MODULE_NOT_FOUND' && e.code !== 'ENOENT') throw e;
}
if (!dishes || typeof dishes !== 'object') dishes = {};

function saveDishes() {
	FS(DISHES_FILE).write(JSON.stringify(dishes));
}

/**
 * Used instead of Dex.packTeam to generate more human-readable output.
 * @param {AnyObject[]} team
 * @param {string[]} ingredients
 */
function stringifyTeam(team, ingredients) {
	let output = '';
	for (let i = 0; i < team.length; i++) {
		const mon = team[i];
		output += `${ingredients[i]} (${mon.species}) @ ${mon.item}<br/>`;
		output += `Ability: ${mon.ability}<br/>`;
		if (mon.happiness !== 255) output += `Happiness: ${mon.happiness}<br/>`;
		let evs = [];
		for (const stat in mon.evs) {
			if (mon.evs[stat]) evs.push(`${mon.evs[stat]} ${stat}`);
		}
		if (evs.length) output += `EVs: ${evs.join(' / ')}<br/>`;
		output += `${mon.nature} Nature<br/>`;
		let ivs = [];
		for (const stat in mon.ivs) {
			if (mon.ivs[stat] !== 31) ivs.push(`${mon.ivs[stat]} ${stat}`);
		}
		if (ivs.length) output += `IVs: ${ivs.join(' / ')}<br/>`;
		output += /** @type {string[]} */ (mon.moves).map(move => `- ${move}<br/>`).join('');
		output += '
';
	}
	return output;
}

/**
 * @param {string} [generator]
 */
function generateTeam(generator = '') {
	let potentialPokemon = Object.keys(Dex.data.Pokedex).filter(mon => {
		const template = Dex.getTemplate(mon);
		return template.baseSpecies === template.species;
	});
	let speciesClause = true;
	switch (generator) {
	case 'ou':
		potentialPokemon = potentialPokemon.filter(mon => {
			const template = Dex.getTemplate(mon);
			return template.tier === 'OU';
		}).concat(potentialPokemon.filter(mon => {
			// There is probably a better way to get the ratios right, oh well.
			const template = Dex.getTemplate(mon);
			return template.tier === 'OU' || template.tier === 'UU';
		}));
		break;
	case 'ag':
		potentialPokemon = potentialPokemon.filter(mon => {
			const template = Dex.getTemplate(mon);
			const unviable = template.tier === 'NFE' || template.tier === 'PU' || template.tier === '(PU)' || template.tier.startsWith("LC");
			const illegal = template.tier === 'Unreleased' || template.tier === 'Illegal' || template.tier.startsWith("CAP");
			return !(unviable || illegal);
		});
		speciesClause = false;
		break;
	default:
		potentialPokemon = potentialPokemon.filter(mon => {
			const template = Dex.getTemplate(mon);
			const op = template.tier === 'AG' || template.tier === 'Uber';
			const unviable = template.tier === 'Illegal' || template.tier.includes("LC");
			return !(op || unviable);
		});
		potentialPokemon.push('miltank', 'miltank', 'miltank', 'miltank'); // 5x chance for miltank for flavor purposes.
	}

	/** @type {string[]} */
	const team = [];

	while (team.length < 6) {
		const randIndex = Math.floor(Math.random() * potentialPokemon.length);
		const potentialMon = potentialPokemon[randIndex];
		if (team.includes(potentialMon)) continue;
		team.push(potentialMon);
		if (speciesClause) potentialPokemon.splice(randIndex, 1);
	}

	return team.map(mon => Dex.getTemplate(mon).species);
}

/**
 * @return {[string, string[]]}
 */
function generateDish() {
	const keys = Object.keys(dishes);
	const entry = dishes[keys[Math.floor(Math.random() * keys.length)]].slice();
	const dish = entry.splice(0, 1)[0];
	const ingredients = [];
	while (ingredients.length < 6) {
		ingredients.push(entry.splice(Math.floor(Math.random() * entry.length), 1)[0]);
	}
	return [dish, ingredients];
}

/** @type {ChatCommands} */
const commands = {
	foodfight: function (target, room, user) {
		if (room !== thecafe) return this.errorReply("This command is only available in The Café.");

		if (!Object.keys(dishes).length) return this.errorReply("No dishes found. Add some dishes first.");

		// @ts-ignore
		if (user.foodfight && user.foodfight.timestamp + FOODFIGHT_COOLDOWN > Date.now()) return this.errorReply("Please wait a few minutes before using this command again.");

		target = toId(target);

		let team, importable;
		const [dish, ingredients] = generateDish();
		if (target === 'bf') {
			const bfTeam = Dex.generateTeam('gen7bssfactory');
			importable = stringifyTeam(bfTeam, ingredients);
			team = /** @type {Template[]} */ (bfTeam).map(val => val.species);
		} else {
			team = generateTeam(target);
		}
		// @ts-ignore
		user.foodfight = {team: team, dish: dish, ingredients: ingredients, timestamp: Date.now()};
		const importStr = importable ? `
Importable team:
${importable}</textarea>
` : '';
		return this.sendReplyBox(`<div class="ladder"><table style="text-align:center;"><tr><th colspan="7" style="font-size:10pt;">Your dish is: <u>${dish}</u></th></tr><tr><th>Team</th>${team.map(mon => `<psicon pokemon="${mon}"/> ${mon}	`).join('')}</tr><tr><th>Ingredients</th>${ingredients.map(ingredient => `${ingredient}	`).join('')}</tr>${importStr}</table></div>`);
	},
	checkfoodfight: function (target, room, user) {
		if (room !== thecafe) return this.errorReply("This command is only available in The Café.");

		const targetUser = this.targetUserOrSelf(target, false);
		if (!targetUser) return this.errorReply(`User ${this.targetUsername} not found.`);
		const self = targetUser === user;
		if (!self && !this.can('mute', targetUser, room)) return false;
		// @ts-ignore
		if (!targetUser.foodfight) return this.errorReply(`${self ? `You don't` : `This user doesn't`} have an active Foodfight team.`);
		// @ts-ignore
		return this.sendReplyBox(`<div class="ladder"><table style="text-align:center;"><tr><th colspan="7" style="font-size:10pt;">${self ? `Your` : `${this.targetUsername}'s`} dish is: <u>${targetUser.foodfight.dish}</u></th></tr><tr><th>Team</th>${targetUser.foodfight.team.map(mon => `<psicon pokemon="${mon}"/> ${mon}	`).join('')}</tr><tr><th>Ingredients</th>${targetUser.foodfight.ingredients.map(ingredient => `${ingredient}	`).join('')}</tr></table></div>`);
	},
	addingredients: 'adddish',
	adddish: function (target, room, user, connection, cmd) {
		if (room !== thecafe) return this.errorReply("This command is only available in The Café.");
		if (!this.can('mute', null, room)) return false;

		let [dish, ...ingredients] = target.split(',');
		dish = dish.trim();
		if (!dish || !ingredients.length) return this.parse('/help foodfight');
		const id = toId(dish);
		if (id === 'constructor') return this.errorReply("Invalid dish name.");
		ingredients = ingredients.map(ingredient => ingredient.trim());

		if ([...ingredients.entries()].some(([index, ingredient]) => ingredients.indexOf(ingredient) !== index)) {
			return this.errorReply("Please don't enter duplicate ingredients.");
		}

		if (ingredients.some(ingredient => ingredient.length > 19)) {
			return this.errorReply("Ingredients can only be 19 characters long.");
		}

		if (cmd === 'adddish') {
			if (dishes[id]) return this.errorReply("This dish already exists.");
			if (ingredients.length < 6) return this.errorReply("Dishes need at least 6 ingredients.");
			dishes[id] = [dish];
		} else {
			if (!dishes[id]) return this.errorReply(`Dish not found: ${dish}`);
			if (ingredients.some(ingredient => dishes[id].includes(ingredient))) return this.errorReply("Please don't enter duplicate ingredients.");
		}

		dishes[id] = dishes[id].concat(ingredients);
		saveDishes();
		this.sendReply(`${cmd.slice(3)} '${dish}: ${ingredients.join(', ')}' added successfully.`);
	},
	removedish: function (target, room, user) {
		if (room !== thecafe) return this.errorReply("This command is only available in The Café.");
		if (!this.can('mute', null, room)) return false;

		const id = toId(target);
		if (id === 'constructor') return this.errorReply("Invalid dish.");
		if (!dishes[id]) return this.errorReply(`Dish '${target}' not found.`);

		delete dishes[id];
		saveDishes();
		this.sendReply(`Dish '${target}' deleted successfully.`);
	},
	viewdishes: function (target, room, user, connection) {
		if (room !== thecafe) return this.errorReply("This command is only available in The Café.");

		return this.parse(`/join view-foodfight`);
	},
	foodfighthelp: [
		`/foodfight <generator> - Gives you a randomly generated Foodfight dish, ingredient list and team. Generator can be either 'ou' or 'ag', or left blank. If left blank, uses the normal Foodfight generator.`,
		`/checkfoodfight <username> - Gives you the last team and dish generated for the entered user, or your own if left blank. Anyone can check their own info, checking other people requires: % @ * # & ~`,
		`/adddish <dish>, <ingredient>, <ingredient>, ... - Adds a dish to the database. Requires: % @ * # & ~`,
		`/addingredients <dish>, <ingredient>, <ingredient>, ... - Adds extra ingredients to a dish in the database. Requires: % @ * # & ~`,
		`/removedish <dish> - Removes a dish from the database. Requires: % @ * # & ~`,
		`/viewdishes - Shows the entire database of dishes. Requires: % @ * # & ~`,
	],
};

exports.commands = commands;

/** @type {PageTable} */
const pages = {
	foodfight(query, user, connection) {
		if (!user.named) return Rooms.RETRY_AFTER_LOGIN;
		let buf = `|title|Foodfight\n|pagehtml|<div class="pad ladder"><h2>Foodfight Dish list</h2>`;
		if (!user.can('mute', null, thecafe)) {
			return buf + `<p>Access denied</p></div>`;
		}
		let content = Object.keys(dishes).map(entry => {
			const [dish, ...ingredients] = dishes[entry];
			return `<tr><td>${dish}</td><td>${ingredients.join(', ')}</td></tr>`;
		}).join('');

		if (!content) {
			buf += `<p>There are no dishes in the database.</p>`;
		} else {
			buf += `<table><tr><th><h3>Dishes</h3></th><th><h3>Ingredients</h3></th></tr>${content}</table>`;
		}
		buf += `</div>`;
		return buf;
	},
};
exports.pages = pages;
<|endoftext|>