<commit_before>/* Copyright 2016 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {
  animationStarted,
  DEFAULT_SCALE,
  DEFAULT_SCALE_VALUE,
  docStyle,
  MAX_SCALE,
  MIN_SCALE,
  noContextMenuHandler,
} from "./ui_utils.js";
import { AnnotationEditorType } from "pdfjs-lib";

const PAGE_NUMBER_LOADING_INDICATOR = "visiblePageIsLoading";

/**
 * @typedef {Object} ToolbarOptions
 * @property {HTMLDivElement} container - Container for the secondary toolbar.
 * @property {HTMLSpanElement} numPages - Label that contains number of pages.
 * @property {HTMLInputElement} pageNumber - Control for display and user input
 *   of the current page number.
 * @property {HTMLSelectElement} scaleSelect - Scale selection control.
 *   Its width is adjusted, when necessary, on UI localization.
 * @property {HTMLOptionElement} customScaleOption - The item used to display
 *   a non-predefined scale.
 * @property {HTMLButtonElement} previous - Button to go to the previous page.
 * @property {HTMLButtonElement} next - Button to go to the next page.
 * @property {HTMLButtonElement} zoomIn - Button to zoom in the pages.
 * @property {HTMLButtonElement} zoomOut - Button to zoom out the pages.
 * @property {HTMLButtonElement} viewFind - Button to open find bar.
 * @property {HTMLButtonElement} openFile - Button to open a new document.
 * @property {HTMLButtonElement} presentationModeButton - Button to switch to
 *   presentation mode.
 * @property {HTMLButtonElement} editorNoneButton - Button to disable editing.
 * @property {HTMLButtonElement} editorFreeTextButton - Button to switch to
 *   FreeText editing.
 * @property {HTMLButtonElement} download - Button to download the document.
 * @property {HTMLAnchorElement} viewBookmark - Button to obtain a bookmark link
 *   to the current location in the document.
 */

class Toolbar {
  /**
   * @param {ToolbarOptions} options
   * @param {EventBus} eventBus
   * @param {IL10n} l10n - Localization service.
   */
  constructor(options, eventBus, l10n) {
    this.toolbar = options.container;
    this.eventBus = eventBus;
    this.l10n = l10n;
    this.buttons = [
      { element: options.previous, eventName: "previouspage" },
      { element: options.next, eventName: "nextpage" },
      { element: options.zoomIn, eventName: "zoomin" },
      { element: options.zoomOut, eventName: "zoomout" },
      { element: options.print, eventName: "print" },
      {
        element: options.presentationModeButton,
        eventName: "presentationmode",
      },
      { element: options.download, eventName: "download" },
      { element: options.viewBookmark, eventName: null },
      {
        element: options.editorNoneButton,
        eventName: "switchannotationeditormode",
        eventDetails: { mode: AnnotationEditorType.NONE },
      },
      {
        element: options.editorFreeTextButton,
        eventName: "switchannotationeditormode",
        eventDetails: { mode: AnnotationEditorType.FREETEXT },
      },
      {
        element: options.editorInkButton,
        eventName: "switchannotationeditormode",
        eventDetails: { mode: AnnotationEditorType.INK },
      },
    ];
    if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
      this.buttons.push({ element: options.openFile, eventName: "openfile" });
    }
    this.items = {
      numPages: options.numPages,
      pageNumber: options.pageNumber,
      scaleSelect: options.scaleSelect,
      customScaleOption: options.customScaleOption,
      previous: options.previous,
      next: options.next,
      zoomIn: options.zoomIn,
      zoomOut: options.zoomOut,
      editorNoneButton: options.editorNoneButton,
      editorFreeTextButton: options.editorFreeTextButton,
      editorFreeTextParamsToolbar: options.editorFreeTextParamsToolbar,
      editorInkButton: options.editorInkButton,
      editorInkParamsToolbar: options.editorInkParamsToolbar,
    };

    this._wasLocalized = false;
    this.reset();

    // Bind the event listeners for click and various other actions.
    this._bindListeners(options);
  }

  setPageNumber(pageNumber, pageLabel) {
    this.pageNumber = pageNumber;
    this.pageLabel = pageLabel;
    this._updateUIState(false);
  }

  setPagesCount(pagesCount, hasPageLabels) {
    this.pagesCount = pagesCount;
    this.hasPageLabels = hasPageLabels;
    this._updateUIState(true);
  }

  setPageScale(pageScaleValue, pageScale) {
    this.pageScaleValue = (pageScaleValue || pageScale).toString();
    this.pageScale = pageScale;
    this._updateUIState(false);
  }

  reset() {
    this.pageNumber = 0;
    this.pageLabel = null;
    this.hasPageLabels = false;
    this.pagesCount = 0;
    this.pageScaleValue = DEFAULT_SCALE_VALUE;
    this.pageScale = DEFAULT_SCALE;
    this._updateUIState(true);
    this.updateLoadingIndicatorState();

    // Reset the Editor buttons too, since they're document specific.
    this.eventBus.dispatch("toolbarreset", { source: this });
  }

  _bindListeners(options) {
    const { pageNumber, scaleSelect } = this.items;
    const self = this;

    // The buttons within the toolbar.
    for (const { element, eventName, eventDetails } of this.buttons) {
      element.addEventListener("click", evt => {
        if (eventName !== null) {
          const details = { source: this };
          if (eventDetails) {
            for (const property in eventDetails) {
              details[property] = eventDetails[property];
            }
          }
          this.eventBus.dispatch(eventName, details);
        }
      });
    }
    // The non-button elements within the toolbar.
    pageNumber.addEventListener("click", function () {
      this.select();
    });
    pageNumber.addEventListener("change", function () {
      self.eventBus.dispatch("pagenumberchanged", {
        source: self,
        value: this.value,
      });
    });

    scaleSelect.addEventListener("change", function () {
      if (this.value === "custom") {
        return;
      }
      self.eventBus.dispatch("scalechanged", {
        source: self,
        value: this.value,
      });
    });
    // Here we depend on browsers dispatching the "click" event *after* the
    // "change" event, when the <select>-element changes.
    scaleSelect.addEventListener("click", function (evt) {
      const target = evt.target;
      // Remove focus when an <option>-element was *clicked*, to improve the UX
      // for mouse users (fixes bug 1300525 and issue 4923).
      if (
        this.value === self.pageScaleValue &&
        target.tagName.toUpperCase() === "OPTION"
      ) {
        this.blur();
      }
    });
    // Suppress context menus for some controls.
    scaleSelect.oncontextmenu = noContextMenuHandler;

    this.eventBus._on("localized", () => {
      this._wasLocalized = true;
      this.#adjustScaleWidth();
      this._updateUIState(true);
    });

    this.#bindEditorToolsListener(options);
  }

  #bindEditorToolsListener({
    editorNoneButton,
    editorFreeTextButton,
    editorFreeTextParamsToolbar,
    editorInkButton,
    editorInkParamsToolbar,
  }) {
    const editorModeChanged = (evt, disableButtons = false) => {
      const editorButtons = [
        { mode: AnnotationEditorType.NONE, button: editorNoneButton },
        {
          mode: AnnotationEditorType.FREETEXT,
          button: editorFreeTextButton,
          toolbar: editorFreeTextParamsToolbar,
        },
        {
          mode: AnnotationEditorType.INK,
          button: editorInkButton,
          toolbar: editorInkParamsToolbar,
        },
      ];

      for (const { mode, button, toolbar } of editorButtons) {
        const checked = mode === evt.mode;
        button.classList.toggle("toggled", checked);
        button.setAttribute("aria-checked", checked);
        button.disabled = disableButtons;
        if (toolbar) {
          toolbar.classList.toggle("hidden", !checked);
        }
      }
    };
    this.eventBus._on("annotationeditormodechanged", editorModeChanged);
    this.eventBus._on("toolbarreset", evt => {
      if (evt.source === this) {
        editorModeChanged(
          { mode: AnnotationEditorType.NONE },
          /* disableButtons = */ true
        );
      }
    });
  }

  _updateUIState(resetNumPages = false) {
    if (!this._wasLocalized) {
      // Don't update the UI state until we localize the toolbar.
      return;
    }
    const { pageNumber, pagesCount, pageScaleValue, pageScale, items } = this;

    if (resetNumPages) {
      if (this.hasPageLabels) {
        items.pageNumber.type = "text";
      } else {
        items.pageNumber.type = "number";
        this.l10n.get("of_pages", { pagesCount }).then(msg => {
          items.numPages.textContent = msg;
        });
      }
      items.pageNumber.max = pagesCount;
    }

    if (this.hasPageLabels) {
      items.pageNumber.value = this.pageLabel;
      this.l10n.get("page_of_pages", { pageNumber, pagesCount }).then(msg => {
        items.numPages.textContent = msg;
      });
    } else {
      items.pageNumber.value = pageNumber;
    }

    items.previous.disabled = pageNumber <= 1;
    items.next.disabled = pageNumber >= pagesCount;

    items.zoomOut.disabled = pageScale <= MIN_SCALE;
    items.zoomIn.disabled = pageScale >= MAX_SCALE;

    this.l10n
      .get("page_scale_percent", { scale: Math.round(pageScale * 10000) / 100 })
      .then(msg => {
        let predefinedValueFound = false;
        for (const option of items.scaleSelect.options) {
          if (option.value !== pageScaleValue) {
            option.selected = false;
            continue;
          }
          option.selected = true;
          predefinedValueFound = true;
        }
        if (!predefinedValueFound) {
          items.customScaleOption.textContent = msg;
          items.customScaleOption.selected = true;
        }
      });
  }

  updateLoadingIndicatorState(loading = false) {
    const { pageNumber } = this.items;

    pageNumber.classList.toggle(PAGE_NUMBER_LOADING_INDICATOR, loading);
  }

  /**
   * Increase the width of the zoom dropdown DOM element if, and only if, it's
   * too narrow to fit the *longest* of the localized strings.
   */
  async #adjustScaleWidth() {
    const { items, l10n } = this;

    const predefinedValuesPromise = Promise.all([
      l10n.get("page_scale_auto"),
      l10n.get("page_scale_actual"),
      l10n.get("page_scale_fit"),
      l10n.get("page_scale_width"),
    ]);
    await animationStarted;

    const style = getComputedStyle(items.scaleSelect),
      scaleSelectContainerWidth = parseInt(
        style.getPropertyValue("--scale-select-container-width"),
        10
      ),
      scaleSelectOverflow = parseInt(
        style.getPropertyValue("--scale-select-overflow"),
        10
      );

    // The temporary canvas is used to measure text length in the DOM.
    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d", { alpha: false });
    ctx.font = `${style.fontSize} ${style.fontFamily}`;

    let maxWidth = 0;
    for (const predefinedValue of await predefinedValuesPromise) {
      const { width } = ctx.measureText(predefinedValue);
      if (width > maxWidth) {
        maxWidth = width;
      }
    }
    maxWidth += 2 * scaleSelectOverflow;

    if (maxWidth > scaleSelectContainerWidth) {
      docStyle.setProperty("--scale-select-container-width", `${maxWidth}px`);
    }
    // Zeroing the width and height cause Firefox to release graphics resources
    // immediately, which can greatly reduce memory consumption.
    canvas.width = 0;
    canvas.height = 0;
  }
}

export { Toolbar };
<commit_msg>Add more private properties/methods in `web/toolbar.js`<commit_after>/* Copyright 2016 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {
  animationStarted,
  DEFAULT_SCALE,
  DEFAULT_SCALE_VALUE,
  docStyle,
  MAX_SCALE,
  MIN_SCALE,
  noContextMenuHandler,
} from "./ui_utils.js";
import { AnnotationEditorType } from "pdfjs-lib";

const PAGE_NUMBER_LOADING_INDICATOR = "visiblePageIsLoading";

/**
 * @typedef {Object} ToolbarOptions
 * @property {HTMLDivElement} container - Container for the secondary toolbar.
 * @property {HTMLSpanElement} numPages - Label that contains number of pages.
 * @property {HTMLInputElement} pageNumber - Control for display and user input
 *   of the current page number.
 * @property {HTMLSelectElement} scaleSelect - Scale selection control.
 *   Its width is adjusted, when necessary, on UI localization.
 * @property {HTMLOptionElement} customScaleOption - The item used to display
 *   a non-predefined scale.
 * @property {HTMLButtonElement} previous - Button to go to the previous page.
 * @property {HTMLButtonElement} next - Button to go to the next page.
 * @property {HTMLButtonElement} zoomIn - Button to zoom in the pages.
 * @property {HTMLButtonElement} zoomOut - Button to zoom out the pages.
 * @property {HTMLButtonElement} viewFind - Button to open find bar.
 * @property {HTMLButtonElement} openFile - Button to open a new document.
 * @property {HTMLButtonElement} presentationModeButton - Button to switch to
 *   presentation mode.
 * @property {HTMLButtonElement} editorNoneButton - Button to disable editing.
 * @property {HTMLButtonElement} editorFreeTextButton - Button to switch to
 *   FreeText editing.
 * @property {HTMLButtonElement} download - Button to download the document.
 * @property {HTMLAnchorElement} viewBookmark - Button to obtain a bookmark link
 *   to the current location in the document.
 */

class Toolbar {
  #wasLocalized = false;

  /**
   * @param {ToolbarOptions} options
   * @param {EventBus} eventBus
   * @param {IL10n} l10n - Localization service.
   */
  constructor(options, eventBus, l10n) {
    this.toolbar = options.container;
    this.eventBus = eventBus;
    this.l10n = l10n;
    this.buttons = [
      { element: options.previous, eventName: "previouspage" },
      { element: options.next, eventName: "nextpage" },
      { element: options.zoomIn, eventName: "zoomin" },
      { element: options.zoomOut, eventName: "zoomout" },
      { element: options.print, eventName: "print" },
      {
        element: options.presentationModeButton,
        eventName: "presentationmode",
      },
      { element: options.download, eventName: "download" },
      { element: options.viewBookmark, eventName: null },
      {
        element: options.editorNoneButton,
        eventName: "switchannotationeditormode",
        eventDetails: { mode: AnnotationEditorType.NONE },
      },
      {
        element: options.editorFreeTextButton,
        eventName: "switchannotationeditormode",
        eventDetails: { mode: AnnotationEditorType.FREETEXT },
      },
      {
        element: options.editorInkButton,
        eventName: "switchannotationeditormode",
        eventDetails: { mode: AnnotationEditorType.INK },
      },
    ];
    if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
      this.buttons.push({ element: options.openFile, eventName: "openfile" });
    }
    this.items = {
      numPages: options.numPages,
      pageNumber: options.pageNumber,
      scaleSelect: options.scaleSelect,
      customScaleOption: options.customScaleOption,
      previous: options.previous,
      next: options.next,
      zoomIn: options.zoomIn,
      zoomOut: options.zoomOut,
      editorNoneButton: options.editorNoneButton,
      editorFreeTextButton: options.editorFreeTextButton,
      editorFreeTextParamsToolbar: options.editorFreeTextParamsToolbar,
      editorInkButton: options.editorInkButton,
      editorInkParamsToolbar: options.editorInkParamsToolbar,
    };

    this.reset();

    // Bind the event listeners for click and various other actions.
    this.#bindListeners(options);
  }

  setPageNumber(pageNumber, pageLabel) {
    this.pageNumber = pageNumber;
    this.pageLabel = pageLabel;
    this.#updateUIState(false);
  }

  setPagesCount(pagesCount, hasPageLabels) {
    this.pagesCount = pagesCount;
    this.hasPageLabels = hasPageLabels;
    this.#updateUIState(true);
  }

  setPageScale(pageScaleValue, pageScale) {
    this.pageScaleValue = (pageScaleValue || pageScale).toString();
    this.pageScale = pageScale;
    this.#updateUIState(false);
  }

  reset() {
    this.pageNumber = 0;
    this.pageLabel = null;
    this.hasPageLabels = false;
    this.pagesCount = 0;
    this.pageScaleValue = DEFAULT_SCALE_VALUE;
    this.pageScale = DEFAULT_SCALE;
    this.#updateUIState(true);
    this.updateLoadingIndicatorState();

    // Reset the Editor buttons too, since they're document specific.
    this.eventBus.dispatch("toolbarreset", { source: this });
  }

  #bindListeners(options) {
    const { pageNumber, scaleSelect } = this.items;
    const self = this;

    // The buttons within the toolbar.
    for (const { element, eventName, eventDetails } of this.buttons) {
      element.addEventListener("click", evt => {
        if (eventName !== null) {
          const details = { source: this };
          if (eventDetails) {
            for (const property in eventDetails) {
              details[property] = eventDetails[property];
            }
          }
          this.eventBus.dispatch(eventName, details);
        }
      });
    }
    // The non-button elements within the toolbar.
    pageNumber.addEventListener("click", function () {
      this.select();
    });
    pageNumber.addEventListener("change", function () {
      self.eventBus.dispatch("pagenumberchanged", {
        source: self,
        value: this.value,
      });
    });

    scaleSelect.addEventListener("change", function () {
      if (this.value === "custom") {
        return;
      }
      self.eventBus.dispatch("scalechanged", {
        source: self,
        value: this.value,
      });
    });
    // Here we depend on browsers dispatching the "click" event *after* the
    // "change" event, when the <select>-element changes.
    scaleSelect.addEventListener("click", function (evt) {
      const target = evt.target;
      // Remove focus when an <option>-element was *clicked*, to improve the UX
      // for mouse users (fixes bug 1300525 and issue 4923).
      if (
        this.value === self.pageScaleValue &&
        target.tagName.toUpperCase() === "OPTION"
      ) {
        this.blur();
      }
    });
    // Suppress context menus for some controls.
    scaleSelect.oncontextmenu = noContextMenuHandler;

    this.eventBus._on("localized", () => {
      this.#wasLocalized = true;
      this.#adjustScaleWidth();
      this.#updateUIState(true);
    });

    this.#bindEditorToolsListener(options);
  }

  #bindEditorToolsListener({
    editorNoneButton,
    editorFreeTextButton,
    editorFreeTextParamsToolbar,
    editorInkButton,
    editorInkParamsToolbar,
  }) {
    const editorModeChanged = (evt, disableButtons = false) => {
      const editorButtons = [
        { mode: AnnotationEditorType.NONE, button: editorNoneButton },
        {
          mode: AnnotationEditorType.FREETEXT,
          button: editorFreeTextButton,
          toolbar: editorFreeTextParamsToolbar,
        },
        {
          mode: AnnotationEditorType.INK,
          button: editorInkButton,
          toolbar: editorInkParamsToolbar,
        },
      ];

      for (const { mode, button, toolbar } of editorButtons) {
        const checked = mode === evt.mode;
        button.classList.toggle("toggled", checked);
        button.setAttribute("aria-checked", checked);
        button.disabled = disableButtons;
        if (toolbar) {
          toolbar.classList.toggle("hidden", !checked);
        }
      }
    };
    this.eventBus._on("annotationeditormodechanged", editorModeChanged);

    this.eventBus._on("toolbarreset", evt => {
      if (evt.source === this) {
        editorModeChanged(
          { mode: AnnotationEditorType.NONE },
          /* disableButtons = */ true
        );
      }
    });
  }

  #updateUIState(resetNumPages = false) {
    if (!this.#wasLocalized) {
      // Don't update the UI state until we localize the toolbar.
      return;
    }
    const { pageNumber, pagesCount, pageScaleValue, pageScale, items } = this;

    if (resetNumPages) {
      if (this.hasPageLabels) {
        items.pageNumber.type = "text";
      } else {
        items.pageNumber.type = "number";
        this.l10n.get("of_pages", { pagesCount }).then(msg => {
          items.numPages.textContent = msg;
        });
      }
      items.pageNumber.max = pagesCount;
    }

    if (this.hasPageLabels) {
      items.pageNumber.value = this.pageLabel;
      this.l10n.get("page_of_pages", { pageNumber, pagesCount }).then(msg => {
        items.numPages.textContent = msg;
      });
    } else {
      items.pageNumber.value = pageNumber;
    }

    items.previous.disabled = pageNumber <= 1;
    items.next.disabled = pageNumber >= pagesCount;

    items.zoomOut.disabled = pageScale <= MIN_SCALE;
    items.zoomIn.disabled = pageScale >= MAX_SCALE;

    this.l10n
      .get("page_scale_percent", { scale: Math.round(pageScale * 10000) / 100 })
      .then(msg => {
        let predefinedValueFound = false;
        for (const option of items.scaleSelect.options) {
          if (option.value !== pageScaleValue) {
            option.selected = false;
            continue;
          }
          option.selected = true;
          predefinedValueFound = true;
        }
        if (!predefinedValueFound) {
          items.customScaleOption.textContent = msg;
          items.customScaleOption.selected = true;
        }
      });
  }

  updateLoadingIndicatorState(loading = false) {
    const { pageNumber } = this.items;

    pageNumber.classList.toggle(PAGE_NUMBER_LOADING_INDICATOR, loading);
  }

  /**
   * Increase the width of the zoom dropdown DOM element if, and only if, it's
   * too narrow to fit the *longest* of the localized strings.
   */
  async #adjustScaleWidth() {
    const { items, l10n } = this;

    const predefinedValuesPromise = Promise.all([
      l10n.get("page_scale_auto"),
      l10n.get("page_scale_actual"),
      l10n.get("page_scale_fit"),
      l10n.get("page_scale_width"),
    ]);
    await animationStarted;

    const style = getComputedStyle(items.scaleSelect),
      scaleSelectContainerWidth = parseInt(
        style.getPropertyValue("--scale-select-container-width"),
        10
      ),
      scaleSelectOverflow = parseInt(
        style.getPropertyValue("--scale-select-overflow"),
        10
      );

    // The temporary canvas is used to measure text length in the DOM.
    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d", { alpha: false });
    ctx.font = `${style.fontSize} ${style.fontFamily}`;

    let maxWidth = 0;
    for (const predefinedValue of await predefinedValuesPromise) {
      const { width } = ctx.measureText(predefinedValue);
      if (width > maxWidth) {
        maxWidth = width;
      }
    }
    maxWidth += 2 * scaleSelectOverflow;

    if (maxWidth > scaleSelectContainerWidth) {
      docStyle.setProperty("--scale-select-container-width", `${maxWidth}px`);
    }
    // Zeroing the width and height cause Firefox to release graphics resources
    // immediately, which can greatly reduce memory consumption.
    canvas.width = 0;
    canvas.height = 0;
  }
}

export { Toolbar };
<|endoftext|>


<commit_before>var http   = require('http'),
    https  = require('https'),
    common = require('../common'),
    passes = exports;

/*!
 * Array of passes.
 *
 * A `pass` is just a function that is executed on `req, socket, options`
 * so that you can easily add new checks while still keeping the base
 * flexible.
 */

/*
 * Websockets Passes
 *
 */

var passes = exports;

[
  /**
   * WebSocket requests must have the `GET` method and
   * the `upgrade:websocket` header
   *
   * @param {ClientRequest} Req Request object
   * @param {Socket} Websocket
   *
   * @api private
   */

  function checkMethodAndHeader (req, socket) {
    if (req.method !== 'GET' || !req.headers.upgrade) {
      socket.destroy();
      return true;
    }

    if (req.headers.upgrade.toLowerCase() !== 'websocket') {
      socket.destroy();
      return true;
    }
  },

  /**
   * Sets `x-forwarded-*` headers if specified in config.
   *
   * @param {ClientRequest} Req Request object
   * @param {Socket} Websocket
   * @param {Object} Options Config object passed to the proxy
   *
   * @api private
   */

  function XHeaders(req, socket, options) {
    if(!options.xfwd) return;

    var values = {
      for  : req.connection.remoteAddress || req.socket.remoteAddress,
      port : common.getPort(req),
      proto: req.connection.pair ? 'wss' : 'ws'
    };

    ['for', 'port', 'proto'].forEach(function(header) {
      req.headers['x-forwarded-' + header] =
        (req.headers['x-forwarded-' + header] || '') +
        (req.headers['x-forwarded-' + header] ? ',' : '') +
        values[header];
    });
  },

  /**
   * Does the actual proxying. Make the request and upgrade it
   * send the Switching Protocols request and pipe the sockets.
   *
   * @param {ClientRequest} Req Request object
   * @param {Socket} Websocket
   * @param {Object} Options Config object passed to the proxy
   *
   * @api private
   */
  function stream(req, socket, options, head, server, clb) {
    common.setupSocket(socket);

    if (head && head.length) socket.unshift(head);

    var proxyReq = (~['https:', 'wss:'].indexOf(options.target.protocol) ? https : http).request(
      common.setupOutgoing(options.ssl || {}, options, req)
    );
    // Error Handler
    proxyReq.on('error', onOutgoingError);

    proxyReq.on('upgrade', function(proxyRes, proxySocket, proxyHead) {
      proxySocket.on('error', onOutgoingError);
      // The pipe below will end proxySocket if socket closes cleanly, but not
      // if it errors (eg, vanishes from the net and starts returning
      // EHOSTUNREACH). We need to do that explicitly.
      socket.on('error', function () {
        proxySocket.end();
      });

      common.setupSocket(proxySocket);

      if (proxyHead && proxyHead.length) proxySocket.unshift(proxyHead);

      socket.write('HTTP/1.1 101 Switching Protocols\r\n');
      socket.write(Object.keys(proxyRes.headers).map(function(i) {
        return i + ": " + proxyRes.headers[i];
      }).join('\r\n') + '\r\n\r\n');
      proxySocket.pipe(socket).pipe(proxySocket);
      // Make sure server exists before we try to emit
      server && server.emit('proxySocket', proxySocket);
    });

    return proxyReq.end(); // XXX: CHECK IF THIS IS THIS CORRECT

    function onOutgoingError(err) {
      if (clb) {
        clb(err, req, socket);
      } else {
        server.emit('error', err, req, socket);
      }
    }
  }

] // <--
  .forEach(function(func) {
    passes[func.name] = func;
  });
<commit_msg>close websocket if proxyReq is closed before upgrade<commit_after>var http   = require('http'),
    https  = require('https'),
    common = require('../common'),
    passes = exports;

/*!
 * Array of passes.
 *
 * A `pass` is just a function that is executed on `req, socket, options`
 * so that you can easily add new checks while still keeping the base
 * flexible.
 */

/*
 * Websockets Passes
 *
 */

var passes = exports;

[
  /**
   * WebSocket requests must have the `GET` method and
   * the `upgrade:websocket` header
   *
   * @param {ClientRequest} Req Request object
   * @param {Socket} Websocket
   *
   * @api private
   */

  function checkMethodAndHeader (req, socket) {
    if (req.method !== 'GET' || !req.headers.upgrade) {
      socket.destroy();
      return true;
    }

    if (req.headers.upgrade.toLowerCase() !== 'websocket') {
      socket.destroy();
      return true;
    }
  },

  /**
   * Sets `x-forwarded-*` headers if specified in config.
   *
   * @param {ClientRequest} Req Request object
   * @param {Socket} Websocket
   * @param {Object} Options Config object passed to the proxy
   *
   * @api private
   */

  function XHeaders(req, socket, options) {
    if(!options.xfwd) return;

    var values = {
      for  : req.connection.remoteAddress || req.socket.remoteAddress,
      port : common.getPort(req),
      proto: req.connection.pair ? 'wss' : 'ws'
    };

    ['for', 'port', 'proto'].forEach(function(header) {
      req.headers['x-forwarded-' + header] =
        (req.headers['x-forwarded-' + header] || '') +
        (req.headers['x-forwarded-' + header] ? ',' : '') +
        values[header];
    });
  },

  /**
   * Does the actual proxying. Make the request and upgrade it
   * send the Switching Protocols request and pipe the sockets.
   *
   * @param {ClientRequest} Req Request object
   * @param {Socket} Websocket
   * @param {Object} Options Config object passed to the proxy
   *
   * @api private
   */
  function stream(req, socket, options, head, server, clb) {
    common.setupSocket(socket);

    if (head && head.length) socket.unshift(head);

    var proxyReq = (~['https:', 'wss:'].indexOf(options.target.protocol) ? https : http).request(
      common.setupOutgoing(options.ssl || {}, options, req)
    );
    // Error Handler
    proxyReq.on('error', onOutgoingError);
    proxyReq.on('response', function (res) {
      // if upgrade event isn't going to happen, close the socket
      if (!res.upgrade) socket.end();
    });

    proxyReq.on('upgrade', function(proxyRes, proxySocket, proxyHead) {
      proxySocket.on('error', onOutgoingError);
      // The pipe below will end proxySocket if socket closes cleanly, but not
      // if it errors (eg, vanishes from the net and starts returning
      // EHOSTUNREACH). We need to do that explicitly.
      socket.on('error', function () {
        proxySocket.end();
      });

      common.setupSocket(proxySocket);

      if (proxyHead && proxyHead.length) proxySocket.unshift(proxyHead);

      socket.write('HTTP/1.1 101 Switching Protocols\r\n');
      socket.write(Object.keys(proxyRes.headers).map(function(i) {
        return i + ": " + proxyRes.headers[i];
      }).join('\r\n') + '\r\n\r\n');
      proxySocket.pipe(socket).pipe(proxySocket);
      // Make sure server exists before we try to emit
      server && server.emit('proxySocket', proxySocket);
    });

    return proxyReq.end(); // XXX: CHECK IF THIS IS THIS CORRECT

    function onOutgoingError(err) {
      if (clb) {
        clb(err, req, socket);
      } else {
        server.emit('error', err, req, socket);
      }
    }
  }

] // <--
  .forEach(function(func) {
    passes[func.name] = func;
  });
<|endoftext|>


<commit_before>var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var Goal = new Schema({
    name: String,
    desc: String,
    completed: {type:Boolean, default: false},
    assigned: {
		type: mongoose.Schema.Types.ObjectId,
		ref: 'Roel'}
    creator: {
		type: mongoose.Schema.Types.ObjectId,
		ref: 'User'},
    created_at: Date,
    updated_at: Date
});
//on every save, add the date
Goal.pre('save', function(next){
	//get the current date
	var currentDate = new Date();
	//change the udpated_at field to current date
	this.updated_at = currentDate;

	//if created_at doesn't exist, add to the field
	if(!this.created_at)
		this.created_at = currentDate;

	next();
});
module.exports = mongoose.model('Goal', Goal);
<commit_msg>edite goal schema, add routes, and add helper functions<commit_after>var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var Goal = new Schema({
    name: String,
    desc: String,
    completed: {type:Boolean, default: false},
    assigned: String,
    creator: {
		type: mongoose.Schema.Types.ObjectId,
		ref: 'User'},
    created_at: Date,
    updated_at: Date
});
//on every save, add the date
Goal.pre('save', function(next){
	//get the current date
	var currentDate = new Date();
	//change the udpated_at field to current date
	this.updated_at = currentDate;

	//if created_at doesn't exist, add to the field
	if(!this.created_at)
		this.created_at = currentDate;

	next();
});
module.exports = mongoose.model('Goal', Goal);
<|endoftext|>


<commit_before>const { mix } = require('laravel-mix');

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

mix.js('resources/assets/js/app.js', 'public/build/js')
    .sass('resources/assets/sass/app.scss', 'public/build/css')
    .version()
    .copy('node_modules/markdown/lib/markdown.js','public/build/custom/markdown.js');
<commit_msg>Add a whitespace<commit_after>const { mix } = require('laravel-mix');

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

mix.js('resources/assets/js/app.js', 'public/build/js')
    .sass('resources/assets/sass/app.scss', 'public/build/css')
    .version()
    .copy('node_modules/markdown/lib/markdown.js', 'public/build/custom/markdown.js');
<|endoftext|>


<commit_before>const merge = require('wpc/merge')

module.exports = env => merge(
    require('wpc/defaults'),
    ({context}) => ({
      env: {
        debug: process.env.DEBUG || ['--debug', '-d', '--verbose'].some(e => process.argv.includes(e)),
        src: context + '/src'
      },
      entry: context + '/src/index.js',
      output: {
        path: context + '/dist'
      },
      devServer: {
        host: '0.0.0.0'
        noInfo: true
      }
    }),

    {env},

    require('wpc/resolve-src'),
    require('wpc/global-modules'),
    require('wpc/global-loaders'),

    require('wpc/define-env'),
    require('wpc/define-version'),

    require('wpc/css'),
    require('wpc/html'),
    require('wpc/coffee'),
    require('wpc/json'),
    require('wpc/yml'),

    require('wpc/babel'),
    require('wpc/babel-es2015'),
    require('wpc/babel-stage3'),

    require('wpc/vue'),
    require('wpc/vue-babel'),
    require('wpc/vue-pug'),
    require('wpc/vue-scss'),
    require('wpc/vue-sass'),
    require('wpc/vue-less'),
    require('wpc/vue-stylus'),

    require('wpc/vendor'),
    require('wpc/manifest'),
    require('wpc/custom'),
    require('wpc/dump-conf'),
    require('wpc/delete-properties')
)
<commit_msg>Fix typo<commit_after>const merge = require('wpc/merge')

module.exports = env => merge(
    require('wpc/defaults'),
    ({context}) => ({
      env: {
        debug: process.env.DEBUG || ['--debug', '-d', '--verbose'].some(e => process.argv.includes(e)),
        src: context + '/src'
      },
      entry: context + '/src/index.js',
      output: {
        path: context + '/dist'
      },
      devServer: {
        host: '0.0.0.0',
        noInfo: true
      }
    }),

    {env},

    require('wpc/resolve-src'),
    require('wpc/global-modules'),
    require('wpc/global-loaders'),

    require('wpc/define-env'),
    require('wpc/define-version'),

    require('wpc/css'),
    require('wpc/html'),
    require('wpc/coffee'),
    require('wpc/json'),
    require('wpc/yml'),

    require('wpc/babel'),
    require('wpc/babel-es2015'),
    require('wpc/babel-stage3'),

    require('wpc/vue'),
    require('wpc/vue-babel'),
    require('wpc/vue-pug'),
    require('wpc/vue-scss'),
    require('wpc/vue-sass'),
    require('wpc/vue-less'),
    require('wpc/vue-stylus'),

    require('wpc/vendor'),
    require('wpc/manifest'),
    require('wpc/custom'),
    require('wpc/dump-conf'),
    require('wpc/delete-properties')
)
<|endoftext|>


<commit_before>
var titleMesh;

function addTitle() {

	var fontLoader new THREE.FontLoader();
	var font = fontLoader.load("fonts/Abscissa_Bold.js");
	//get text from hash
	var theTitle = "unExplore";
	var hash = document.location.hash.substr(1);
	if (hash.length !== 0) {
		theTitle = hash;
	}
	var geometry = new THREE.TextGeometry(theTitle, {
		font: font,
		size: 80,
		height: 20,
		curveSegments: 2
	});
	var materials = [
		new THREE.MeshPhongMaterial({color: 0x8c1717} ), //front
		new THREE.MeshPhongMaterial({color: 0x8c1717} )//side
	];

	titleMesh = new THREE.Mesh(geometry, materials);
	titleMesh.position.set = (0,0,0)
}

<commit_msg>Update unexplore.js<commit_after>
var material = new THREE.MeshPhongMaterial({
	color: 0x8c1717
});
var textGeom = new THREE.TextGeometry('unExplore', {
	font: 'abscissa_bold'
});
var titleMesh = new THREE.Mesh(textGeom, material);
<|endoftext|>


<commit_before>module.exports = {
  app_host_port: 'streetmix-v2.herokuapp.com',
  header_host_port: 'streetmix-v2.herokuapp.com',
  restapi_baseuri: 'http://streetmix-api.herokuapp.com',
  facebook_app_id: '162729607241489'
}

<commit_msg>Revert "Testing domain issue with requests to API."<commit_after>module.exports = {
  app_host_port: 'streetmix.net',
  header_host_port: 'streetmix-v2.herokuapp.com',
  restapi_baseuri: 'http://streetmix-api.herokuapp.com',
  facebook_app_id: '162729607241489'
}

<|endoftext|>


<commit_before>exports.BattleScripts = {
    init: function() {
        for (var i in this.data.FormatsData)
            this.data.FormatsData[i].dreamWorldRelease = true;
            },

                this.data.Pokedex.clefable.types = ["Normal","Psychic"];
                this.data.Pokedex.clefable.abilities['0'] = 'Friend Guard';

                this.data.Pokedex.arcanine.abilities['1'] = 'Adrenaline';

                this.data.Pokedex.electrode.abilities['1'] = 'Volt Absorb';

                this.data.Pokedex.exeggutor.abilities['1'] = 'Trickster';

                this.data.Pokedex.weezing.types = ["Poison","Electric"];

                this.data.Pokedex.chansey.abilities['1'] = 'Friend Guard';

                this.data.Pokedex.jynx.abilities['0'] = 'Caution';

                this.data.Pokedex.dragonair.types = ["Dragon","Water"];

                this.data.Pokedex.mewtwo.abilities['0'] = 'Dauntless';

                this.data.Pokedex.piloswine.abilities['0'] = 'Arctic Rush';

                this.data.Pokedex.skarmory.abilities['0'] = 'Ancient Wind';

                this.data.Pokedex.mantine.abilities['DW'] = 'Storm Drain';

                this.data.Pokedex.kingdra.abilities['DW'] = 'Water Veil';

                this.data.Pokedex.blissey.abilities['1'] = 'Friend Guard';

                this.data.Pokedex.blaziken.abilities['DW'] = 'Adrenaline';

                this.data.Pokedex.gardevoir.abilities['0'] = 'Caution';
                this.data.Pokedex.gardevoir.abilities['DW'] = 'Justified';

                this.data.Pokedex.cacturne.abilities['0'] = 'Sand Rush';

                this.data.Pokedex.wailord.abilities['0'] = 'Hydration';

                this.data.Pokedex.claydol.abilities['0'] = 'Magic Guard';
                this.data.Pokedex.claydol.abilities['DW'] = 'Caution';

                this.data.Pokedex.milotic.abilities['0'] = 'Multiscale';

                this.data.Pokedex.metagross.abilities['1'] = 'Light Metal';
                this.data.Pokedex.metagross.abilities['DW'] = 'Gravotonize';

                this.data.Pokedex.regirock.abilities['DW'] = 'Sand Veil';

                this.data.Pokedex.registeel.abilities['DW'] = 'Heavy Metal';

                this.data.Pokedex.luxray.types = ["Electric","Dark"];

                this.data.Pokedex.bastiodon.types = ["Steel"];

                this.data.Pokedex.drifblim.abilities['0'] = 'Tempest';

                this.data.Pokedex.spiritomb.abilities['DW'] = 'Trickster';

                this.data.Pokedex.garchomp.abilities['DW'] = 'Mach Scale';

                this.data.Pokedex.hippowdon.abilities['DW'] = 'Sand Veil';

                this.data.Pokedex.lumineon.types = ["Water","Flying"];

                this.data.Pokedex.weavile.abilities['0'] = 'Technician';

                this.data.Pokedex.electivire.types = ["Electric","Fighting"];

                this.data.Pokedex.yanmega.types = ["Bug","Dragon"];
                this.data.Pokedex.yanmega.abilities['1'] = 'Compoundeyes';

                this.data.Pokedex.glaceon.abilities['0'] = 'Arctic Rush';

                this.data.Pokedex.mamoswine.abilities['0'] = 'Arctic Rush';

                this.data.Pokedex.gallade.abilities['0'] = 'Caution';
                this.data.Pokedex.gallade.abilities['1'] = 'Trace';

                this.data.Pokedex.dusknoir.types = ["Ghost","Fighting"];
                this.data.Pokedex.dusknoir.abilities['0'] = 'Iron Fist';

                this.data.Pokedex.serperior.types = ["Grass","Dragon"];

                this.data.Pokedex.emboar.types = ["Fire","Dark"];

                this.data.Pokedex.musharna.types = ["Psychic","Ghost"];
                this.data.Pokedex.musharna.abilities['1'] = 'Caution';

                this.data.Pokedex.darmanitan.types = ["Fire","Fighting"];
                this.data.Pokedex.darmanitan.abilities['1'] = 'Reckless';

                this.data.Pokedex.sigilyph.abilities['0'] = 'Ancient Wind';

                this.data.Pokedex.cofagrigus.types = ["Ghost","Steel"];

                this.data.Pokedex.gothitelle.types = ["Psychic","Dark"];

                this.data.Pokedex.reuniclus.abilities['0'] = 'Gravotonize';

                this.data.Pokedex.zoroark.types = ["Dark","Ghost"];
                this.data.Pokedex.zoroark.abilities['DW'] = 'Dauntless';

                this.data.Pokedex.swanna.abilities['0'] = 'Water Veil';

                this.data.Pokedex.vanilluxe.abilities['1'] = 'Snow Warning';

                this.data.Pokedex.beheeyem.types = ["Psychic","Electric"];
                this.data.Pokedex.beheeyem.abilities['0'] = 'Levitate';
                this.data.Pokedex.beheeyem.abilities['1'] = 'Dauntless';

                this.data.Pokedex.beartic.types = ["Ice","Fighting"];
                this.data.Pokedex.beartic.abilities['0'] = 'Arctic Rush';

                this.data.Pokedex.cryogonal.types = ["Ice","Steel"];

                this.data.Pokedex.druddigon.types = ["Dragon","Rock"];
                this.data.Pokedex.druddigon.abilities['0'] = 'Mach Scale';
                this.data.Pokedex.druddigon.abilities['1'] = 'Solidify';
                this.data.Pokedex.druddigon.abilities['DW'] = 'Marvel Scale';

                this.data.Pokedex.tornadustherian.abilities['0'] = 'Tempest';

                this.data.Pokedex.kyurem.abilities['0'] = 'Arctic Rush';

                this.data.Pokedex.genesect.abilities['0'] = 'Compoundeyes';
        }
};

<commit_msg>Update scripts.js<commit_after>exports.BattleScripts = {

                this.data.Pokedex.clefable.types = ["Normal","Psychic"];
                this.data.Pokedex.clefable.abilities['0'] = 'Friend Guard';

                this.data.Pokedex.arcanine.abilities['1'] = 'Adrenaline';

                this.data.Pokedex.electrode.abilities['1'] = 'Volt Absorb';

                this.data.Pokedex.exeggutor.abilities['1'] = 'Trickster';

                this.data.Pokedex.weezing.types = ["Poison","Electric"];

                this.data.Pokedex.chansey.abilities['1'] = 'Friend Guard';

                this.data.Pokedex.jynx.abilities['0'] = 'Caution';

                this.data.Pokedex.dragonair.types = ["Dragon","Water"];

                this.data.Pokedex.mewtwo.abilities['0'] = 'Dauntless';

                this.data.Pokedex.piloswine.abilities['0'] = 'Arctic Rush';

                this.data.Pokedex.skarmory.abilities['0'] = 'Ancient Wind';

                this.data.Pokedex.mantine.abilities['DW'] = 'Storm Drain';

                this.data.Pokedex.kingdra.abilities['DW'] = 'Water Veil';

                this.data.Pokedex.blissey.abilities['1'] = 'Friend Guard';

                this.data.Pokedex.blaziken.abilities['DW'] = 'Adrenaline';

                this.data.Pokedex.gardevoir.abilities['0'] = 'Caution';
                this.data.Pokedex.gardevoir.abilities['DW'] = 'Justified';

                this.data.Pokedex.cacturne.abilities['0'] = 'Sand Rush';

                this.data.Pokedex.wailord.abilities['0'] = 'Hydration';

                this.data.Pokedex.claydol.abilities['0'] = 'Magic Guard';
                this.data.Pokedex.claydol.abilities['DW'] = 'Caution';

                this.data.Pokedex.milotic.abilities['0'] = 'Multiscale';

                this.data.Pokedex.metagross.abilities['1'] = 'Light Metal';
                this.data.Pokedex.metagross.abilities['DW'] = 'Gravotonize';

                this.data.Pokedex.regirock.abilities['DW'] = 'Sand Veil';

                this.data.Pokedex.registeel.abilities['DW'] = 'Heavy Metal';

                this.data.Pokedex.luxray.types = ["Electric","Dark"];

                this.data.Pokedex.bastiodon.types = ["Steel"];

                this.data.Pokedex.drifblim.abilities['0'] = 'Tempest';

                this.data.Pokedex.spiritomb.abilities['DW'] = 'Trickster';

                this.data.Pokedex.garchomp.abilities['DW'] = 'Mach Scale';

                this.data.Pokedex.hippowdon.abilities['DW'] = 'Sand Veil';

                this.data.Pokedex.lumineon.types = ["Water","Flying"];

                this.data.Pokedex.weavile.abilities['0'] = 'Technician';

                this.data.Pokedex.electivire.types = ["Electric","Fighting"];

                this.data.Pokedex.yanmega.types = ["Bug","Dragon"];
                this.data.Pokedex.yanmega.abilities['1'] = 'Compoundeyes';

                this.data.Pokedex.glaceon.abilities['0'] = 'Arctic Rush';

                this.data.Pokedex.mamoswine.abilities['0'] = 'Arctic Rush';

                this.data.Pokedex.gallade.abilities['0'] = 'Caution';
                this.data.Pokedex.gallade.abilities['1'] = 'Trace';

                this.data.Pokedex.dusknoir.types = ["Ghost","Fighting"];
                this.data.Pokedex.dusknoir.abilities['0'] = 'Iron Fist';

                this.data.Pokedex.serperior.types = ["Grass","Dragon"];

                this.data.Pokedex.emboar.types = ["Fire","Dark"];

                this.data.Pokedex.musharna.types = ["Psychic","Ghost"];
                this.data.Pokedex.musharna.abilities['1'] = 'Caution';

                this.data.Pokedex.darmanitan.types = ["Fire","Fighting"];
                this.data.Pokedex.darmanitan.abilities['1'] = 'Reckless';

                this.data.Pokedex.sigilyph.abilities['0'] = 'Ancient Wind';

                this.data.Pokedex.cofagrigus.types = ["Ghost","Steel"];

                this.data.Pokedex.gothitelle.types = ["Psychic","Dark"];

                this.data.Pokedex.reuniclus.abilities['0'] = 'Gravotonize';

                this.data.Pokedex.zoroark.types = ["Dark","Ghost"];
                this.data.Pokedex.zoroark.abilities['DW'] = 'Dauntless';

                this.data.Pokedex.swanna.abilities['0'] = 'Water Veil';

                this.data.Pokedex.vanilluxe.abilities['1'] = 'Snow Warning';

                this.data.Pokedex.beheeyem.types = ["Psychic","Electric"];
                this.data.Pokedex.beheeyem.abilities['0'] = 'Levitate';
                this.data.Pokedex.beheeyem.abilities['1'] = 'Dauntless';

                this.data.Pokedex.beartic.types = ["Ice","Fighting"];
                this.data.Pokedex.beartic.abilities['0'] = 'Arctic Rush';

                this.data.Pokedex.cryogonal.types = ["Ice","Steel"];

                this.data.Pokedex.druddigon.types = ["Dragon","Rock"];
                this.data.Pokedex.druddigon.abilities['0'] = 'Mach Scale';
                this.data.Pokedex.druddigon.abilities['1'] = 'Solidify';
                this.data.Pokedex.druddigon.abilities['DW'] = 'Marvel Scale';

                this.data.Pokedex.tornadustherian.abilities['0'] = 'Tempest';

                this.data.Pokedex.kyurem.abilities['0'] = 'Arctic Rush';

                this.data.Pokedex.genesect.abilities['0'] = 'Compoundeyes';
        }
};

<|endoftext|>


<commit_before>'use strict';

let Promise = require('bluebird');
let debug = require("debug")("bot-express:flow");
let apiai = require('apiai');

module.exports = class Flow {
    constructor(vp, bot_event, context, options){
        this.vp = vp;
        this.bot_event = bot_event;
        this.context = context;
        this.default_intent = options.default_intent;
        this.default_skill = options.default_skill;
        this.skill_path = options.skill_path;
        this.skill = this._instantiate_skill(this.context.intent.action);
        this.vp.skill = this.skill;

        if (!!this.skill.required_parameter && typeof this.skill.required_parameter == "object"){
            debug(`This skill requires ${Object.keys(this.skill.required_parameter).length} parameters.`);
        } else {
            debug(`This skill requires 0 parameters.`);
        }

        // At the very first time of the conversation, we identify to_confirm parameters by required_parameter in skill file.
        // After that, we depend on context.to_confirm to identify to_confirm parameters.
        if (this.context.to_confirm.length == 0){
            this.context.to_confirm = this._identify_to_confirm_parameter(this.skill.required_parameter, this.context.confirmed);
        }
        debug(`We have ${this.context.to_confirm.length} parameters to confirm.`);

        this.context.previous.message.unshift({
            from: "user",
            message: vp.extract_message()
        });
    }

    _instantiate_skill(intent){
        if (!intent){
            debug("Intent should have been set but not.");
            return;
        }

        let skill;
        // If the intent is not identified, we use default_skill.
        if (intent == this.default_intent){
            skill = this.default_skill;
        } else {
            skill = intent;
        }

        let skill_instance;

        if (skill == "builtin_default"){
            debug("Use built-in default skill.");
            let skill_class = require("../skill/default");
            skill_instance = new skill_class();
        } else {
            debug(`Look for ${skill} skill.`);
            let skill_class;
            try {
                skill_class = require(`${this.skill_path}${skill}`);
                debug("Skill found.")
            } catch(exception){
                debug("Skill not found.");
                throw(exception);
            }
            skill_instance = new skill_class();
        }

        return skill_instance;
    }

    _identify_to_confirm_parameter(required_parameter, confirmed){
        let to_confirm = [];

        // If there is no required_parameter, we just return empty object as confirmed.
        if (!required_parameter){
            return to_confirm;
        }

        // Scan confirmed parameters and if missing required parameters found, we add them to to_confirm.
        for (let req_param_key of Object.keys(required_parameter)){
            if (typeof confirmed[req_param_key] == "undefined"){
                to_confirm.push({
                    name: req_param_key,
                    label: required_parameter[req_param_key].label,
                    message_to_confirm: required_parameter[req_param_key].message_to_confirm,
                    parser: required_parameter[req_param_key].parser,
                    reaction: required_parameter[req_param_key].reaction
                });
            }
        }
        return to_confirm;
    }

    _collect(){
        if (this.context.to_confirm.length == 0){
            debug("While collect() is called, there is no parameter to confirm.");
            return Promise.reject();
        }
        let message;

        if (!!this.context.to_confirm[0].message_to_confirm[this.vp.type]){
            // Found message platform specific message object.
            debug("Found message platform specific message object.");
            message = this.context.to_confirm[0].message_to_confirm[this.vp.type];
        } else if (!!this.context.to_confirm[0].message_to_confirm){
            // Found common message object. We compile this message object to get message platform specific message object.
            debug("Found common message object.");
            message = this.context.to_confirm[0].message_to_confirm;
        } else {
            debug("While we need to send a message to confirm parameter, the message not found.");
            return Promise.reject();
        }
        debug(message);

        // Set confirming.
        this.context.confirming = this.context.to_confirm[0].name;

        // Send question to the user.
        return this.vp.reply([message]);
    }

    change_parameter(key, value){
        return this.add_parameter(key, value, true);
    }

    add_parameter(key, value, is_change = false){
        return new Promise((resolve, reject) => {
            debug(`Parsing parameter {${key}: "${value}"}`);

            let value_parsed;

            // Parse the value. If the value is not suitable for this key, exception will be thrown.
            if (!!this.skill.required_parameter && !!this.skill.required_parameter[key]){
                debug("This value is for required parameter.");

                if (!!this.skill.required_parameter[key].parser){
                    debug("parse method found.");
                    value_parsed = new Promise((resolve, reject) => {
                        this.skill.required_parameter[key].parser(value, resolve, reject);
                    });
                } else if (!!this.skill["parse_" + key]){
                    debug("parse method found.");
                    value_parsed = new Promise((resolve, reject) => {
                        this.skill["parse_" + key](value, resolve, reject);
                    });
                } else {
                    debug("parse method not found. We use the value as is as long as the value is set.");
                    // If parse method is not implemented, we use the value as-is.
                    if (value === null || value == ""){
                        value_parsed = Promise.reject();
                    } else {
                        value_parsed = Promise.resolve(value);
                    }
                }
            } else if (!!this.skill.optional_parameter && !!this.skill.optional_parameter[key]){
                debug("This value is for optional parameter.");
                if (!!this.skill.optional_parameter[key].parser){
                    debug("parse method found.");
                    value_parsed = new Promise((resolve, reject) => {
                        this.skill.optional_parameter[key].parser(value, resolve, reject);
                    });
                } else if (!!this.skill["parse_" + key]){
                    value_parsed = new Promise((resolve, reject) => {
                        this.skill["parse_" + key](value, resolve, reject);
                    });
                } else {
                    debug("parse method not found. We use the value as is as long as the value is set.");
                    // If parse method is not implemented, we use the value as-is.
                    if (value === null || value == ""){
                        value_parsed = Promise.reject();
                    } else {
                        value_parsed = Promise.resolve(value);
                    }
                }
            } else {
                // This is not the parameter we care about. So skip it.
                debug("This is not the parameter we care about.");
                return reject("This is not the parameter we care about.");
            }

            return value_parsed.then(
                (response) => {
                    debug(`Parse succeeded. Going to add parameter {${key}: "${response}"}`);

                    let parsed_value = response;

                    // Add the parameter to "confirmed".
                    let param = {};
                    param[key] = parsed_value;
                    Object.assign(this.context.confirmed, param);

                    // At the same time, add the parameter key to previously confirmed list. The order of this list is newest first.
                    if (!is_change){
                        this.context.previous.confirmed.unshift(key);
                    }

                    // Remove item from to_confirm.
                    let index_to_remove = this.context.to_confirm.findIndex(param => param.name === key);
                    if (index_to_remove !== -1){
                        debug(`Removing ${param.name} from to_confirm.`);
                        this.context.to_confirm.splice(index_to_remove, 1);
                    }

                    // Clear confirming.
                    if (this.context.confirming == key){
                        this.context.confirming = null;
                    }

                    debug(`We have ${this.context.to_confirm.length} parameters to confirm.`);
                    return resolve(param);
                }
            ).catch(
                (exception) => {
                    // This means user defined skill says this value does not fit to this parameter.
                    debug("Exception: The value does not fit to this parameter.");
                    return reject("The value does not fit to this parameter.");
                }
                /*
                (response) => {
                    // This means user defined skill says this value does not fit to this parameter.
                    debug("The value does not fit to this parameter.");
                    return reject("The value does not fit to this parameter.");
                }*/
            );
        });
    }

    react(parse_result, key, value){
        if (this.skill.required_parameter && this.skill.required_parameter[key]){
            if (!!this.skill.required_parameter[key].reaction){
                // This parameter has reaction. So do it and return its promise.
                debug(`Perform reaction. Param value is ${value}`);
                return this.skill.required_parameter[key].reaction(parse_result, value, this.vp);
            } else if (!!this.skill["reaction_" + key]){
                // This parameter has reaction. So do it and return its promise.
                debug(`Perform reaction. Param value is ${value}`);
                return this.skill["reaction_" + key](parse_result, value, this.vp);
            } else {
                // This parameter does not have reaction so do nothing.
                debug(`We have no reaction to perform.`);
                return Promise.resolve();
            }
        } else if (this.skill.optional_parameter && this.skill.optional_parameter[key]){
            if (!!this.skill.optional_parameter[key].reaction){
                // This parameter has reaction. So do it and return its promise.
                debug(`Perform reaction. Param value is ${value}`);
                return this.skill.optional_parameter[key].reaction(parse_result, value, this.vp);
            } else if (!!this.skill["reaction_" + key]){
                // This parameter has reaction. So do it and return its promise.
                debug(`Perform reaction. Param value is ${value}`);
                return this.skill["reaction_" + key](parse_result, value, this.vp);
            } else {
                // This parameter does not have reaction so do nothing.
                debug(`We have no reaction to perform.`);
                return Promise.resolve();
            }
        }
    }

    ask_retry(message_text){
        let messages = [this.vp.create_text_message(message_text)];
        return this.vp.reply(messages);
    }

    finish(){
        // If we still have parameters to confirm, we collect them.
        if (this.context.to_confirm.length > 0){
            debug("Going to collect parameter.");
            return this._collect();
        }

        // If we have no parameters to confirm, we finish this conversation using finish method of skill.
        debug("Going to perform final action.");
        let finished = new Promise((resolve, reject) => {
            this.skill.finish(this.vp, this.bot_event, this.context, resolve, reject);
        });

        return finished.then(
            (response) => {
                debug("Final action succeeded.");
                // Double check if we have no parameters to confirm since developers can execute collect() method inside finsh().
                if (this.context.to_confirm.length > 0){
                    debug("Going to collect parameter.");
                    return this._collect();
                }

                debug("Final action done. Wrapping up.");
                if (this.skill.clear_context_on_finish && this.context.to_confirm.length == 0){
                    debug(`Clearing context.`);
                    this.context = null;
                }
                return response;
            },
            (response) => {
                debug("Final action failed.");
                return Promise.reject(response);
            }
        );
        /*
        debug("Going to perform final action.");
        return this.skill.finish(this.vp, this.bot_event, this.context).then(
            (response) => {
                // Double check if we have no parameters to confirm since developers can execute collect() method inside finsh().
                if (this.context.to_confirm.length > 0){
                    debug("Going to collect parameter.");
                    return this._collect();
                }

                debug("Final action done. Wrapping up.");
                if (this.skill.clear_context_on_finish && this.context.to_confirm.length == 0){
                    debug(`Clearing context.`);
                    this.context = null;
                }
                return response;
            },
            (response) => {
                return Promise.reject(response);
            }
        );
        */
    }
};
<commit_msg>chaning finish just like parse<commit_after>'use strict';

let Promise = require('bluebird');
let debug = require("debug")("bot-express:flow");
let apiai = require('apiai');

module.exports = class Flow {
    constructor(vp, bot_event, context, options){
        this.vp = vp;
        this.bot_event = bot_event;
        this.context = context;
        this.default_intent = options.default_intent;
        this.default_skill = options.default_skill;
        this.skill_path = options.skill_path;
        this.skill = this._instantiate_skill(this.context.intent.action);
        this.vp.skill = this.skill;

        if (!!this.skill.required_parameter && typeof this.skill.required_parameter == "object"){
            debug(`This skill requires ${Object.keys(this.skill.required_parameter).length} parameters.`);
        } else {
            debug(`This skill requires 0 parameters.`);
        }

        // At the very first time of the conversation, we identify to_confirm parameters by required_parameter in skill file.
        // After that, we depend on context.to_confirm to identify to_confirm parameters.
        if (this.context.to_confirm.length == 0){
            this.context.to_confirm = this._identify_to_confirm_parameter(this.skill.required_parameter, this.context.confirmed);
        }
        debug(`We have ${this.context.to_confirm.length} parameters to confirm.`);

        this.context.previous.message.unshift({
            from: "user",
            message: vp.extract_message()
        });
    }

    _instantiate_skill(intent){
        if (!intent){
            debug("Intent should have been set but not.");
            return;
        }

        let skill;
        // If the intent is not identified, we use default_skill.
        if (intent == this.default_intent){
            skill = this.default_skill;
        } else {
            skill = intent;
        }

        let skill_instance;

        if (skill == "builtin_default"){
            debug("Use built-in default skill.");
            let skill_class = require("../skill/default");
            skill_instance = new skill_class();
        } else {
            debug(`Look for ${skill} skill.`);
            let skill_class;
            try {
                skill_class = require(`${this.skill_path}${skill}`);
                debug("Skill found.")
            } catch(exception){
                debug("Skill not found.");
                throw(exception);
            }
            skill_instance = new skill_class();
        }

        return skill_instance;
    }

    _identify_to_confirm_parameter(required_parameter, confirmed){
        let to_confirm = [];

        // If there is no required_parameter, we just return empty object as confirmed.
        if (!required_parameter){
            return to_confirm;
        }

        // Scan confirmed parameters and if missing required parameters found, we add them to to_confirm.
        for (let req_param_key of Object.keys(required_parameter)){
            if (typeof confirmed[req_param_key] == "undefined"){
                to_confirm.push({
                    name: req_param_key,
                    label: required_parameter[req_param_key].label,
                    message_to_confirm: required_parameter[req_param_key].message_to_confirm,
                    parser: required_parameter[req_param_key].parser,
                    reaction: required_parameter[req_param_key].reaction
                });
            }
        }
        return to_confirm;
    }

    _collect(){
        if (this.context.to_confirm.length == 0){
            debug("While collect() is called, there is no parameter to confirm.");
            return Promise.reject();
        }
        let message;

        if (!!this.context.to_confirm[0].message_to_confirm[this.vp.type]){
            // Found message platform specific message object.
            debug("Found message platform specific message object.");
            message = this.context.to_confirm[0].message_to_confirm[this.vp.type];
        } else if (!!this.context.to_confirm[0].message_to_confirm){
            // Found common message object. We compile this message object to get message platform specific message object.
            debug("Found common message object.");
            message = this.context.to_confirm[0].message_to_confirm;
        } else {
            debug("While we need to send a message to confirm parameter, the message not found.");
            return Promise.reject();
        }
        debug(message);

        // Set confirming.
        this.context.confirming = this.context.to_confirm[0].name;

        // Send question to the user.
        return this.vp.reply([message]);
    }

    change_parameter(key, value){
        return this.add_parameter(key, value, true);
    }

    add_parameter(key, value, is_change = false){
        return new Promise((resolve, reject) => {
            debug(`Parsing parameter {${key}: "${value}"}`);

            let value_parsed;

            // Parse the value. If the value is not suitable for this key, exception will be thrown.
            if (!!this.skill.required_parameter && !!this.skill.required_parameter[key]){
                debug("This value is for required parameter.");

                if (!!this.skill.required_parameter[key].parser){
                    debug("parse method found.");
                    value_parsed = new Promise((resolve, reject) => {
                        this.skill.required_parameter[key].parser(value, resolve, reject);
                    });
                } else if (!!this.skill["parse_" + key]){
                    debug("parse method found.");
                    value_parsed = new Promise((resolve, reject) => {
                        this.skill["parse_" + key](value, resolve, reject);
                    });
                } else {
                    debug("parse method not found. We use the value as is as long as the value is set.");
                    // If parse method is not implemented, we use the value as-is.
                    if (value === null || value == ""){
                        value_parsed = Promise.reject();
                    } else {
                        value_parsed = Promise.resolve(value);
                    }
                }
            } else if (!!this.skill.optional_parameter && !!this.skill.optional_parameter[key]){
                debug("This value is for optional parameter.");
                if (!!this.skill.optional_parameter[key].parser){
                    debug("parse method found.");
                    value_parsed = new Promise((resolve, reject) => {
                        this.skill.optional_parameter[key].parser(value, resolve, reject);
                    });
                } else if (!!this.skill["parse_" + key]){
                    value_parsed = new Promise((resolve, reject) => {
                        this.skill["parse_" + key](value, resolve, reject);
                    });
                } else {
                    debug("parse method not found. We use the value as is as long as the value is set.");
                    // If parse method is not implemented, we use the value as-is.
                    if (value === null || value == ""){
                        value_parsed = Promise.reject();
                    } else {
                        value_parsed = Promise.resolve(value);
                    }
                }
            } else {
                // This is not the parameter we care about. So skip it.
                debug("This is not the parameter we care about.");
                return reject("This is not the parameter we care about.");
            }

            return value_parsed.then(
                (response) => {
                    debug(`Parse succeeded. Going to add parameter {${key}: "${response}"}`);

                    let parsed_value = response;

                    // Add the parameter to "confirmed".
                    let param = {};
                    param[key] = parsed_value;
                    Object.assign(this.context.confirmed, param);

                    // At the same time, add the parameter key to previously confirmed list. The order of this list is newest first.
                    if (!is_change){
                        this.context.previous.confirmed.unshift(key);
                    }

                    // Remove item from to_confirm.
                    let index_to_remove = this.context.to_confirm.findIndex(param => param.name === key);
                    if (index_to_remove !== -1){
                        debug(`Removing ${param.name} from to_confirm.`);
                        this.context.to_confirm.splice(index_to_remove, 1);
                    }

                    // Clear confirming.
                    if (this.context.confirming == key){
                        this.context.confirming = null;
                    }

                    debug(`We have ${this.context.to_confirm.length} parameters to confirm.`);
                    return resolve(param);
                },
                (response) => {
                    // This means user defined skill says this value does not fit to this parameter.
                    debug("Rejected: The value does not fit to this parameter.");
                    return reject("The value does not fit to this parameter.");
                }
            ).catch(
                (exception) => {
                    // This means user defined skill says this value does not fit to this parameter.
                    debug("Exception: The value does not fit to this parameter.");
                    return reject("The value does not fit to this parameter.");
                }
                /*
                (response) => {
                    // This means user defined skill says this value does not fit to this parameter.
                    debug("The value does not fit to this parameter.");
                    return reject("The value does not fit to this parameter.");
                }*/
            );
        });
    }

    react(parse_result, key, value){
        if (this.skill.required_parameter && this.skill.required_parameter[key]){
            if (!!this.skill.required_parameter[key].reaction){
                // This parameter has reaction. So do it and return its promise.
                debug(`Perform reaction. Param value is ${value}`);
                return this.skill.required_parameter[key].reaction(parse_result, value, this.vp);
            } else if (!!this.skill["reaction_" + key]){
                // This parameter has reaction. So do it and return its promise.
                debug(`Perform reaction. Param value is ${value}`);
                return this.skill["reaction_" + key](parse_result, value, this.vp);
            } else {
                // This parameter does not have reaction so do nothing.
                debug(`We have no reaction to perform.`);
                return Promise.resolve();
            }
        } else if (this.skill.optional_parameter && this.skill.optional_parameter[key]){
            if (!!this.skill.optional_parameter[key].reaction){
                // This parameter has reaction. So do it and return its promise.
                debug(`Perform reaction. Param value is ${value}`);
                return this.skill.optional_parameter[key].reaction(parse_result, value, this.vp);
            } else if (!!this.skill["reaction_" + key]){
                // This parameter has reaction. So do it and return its promise.
                debug(`Perform reaction. Param value is ${value}`);
                return this.skill["reaction_" + key](parse_result, value, this.vp);
            } else {
                // This parameter does not have reaction so do nothing.
                debug(`We have no reaction to perform.`);
                return Promise.resolve();
            }
        }
    }

    ask_retry(message_text){
        let messages = [this.vp.create_text_message(message_text)];
        return this.vp.reply(messages);
    }

    finish(){
        // If we still have parameters to confirm, we collect them.
        if (this.context.to_confirm.length > 0){
            debug("Going to collect parameter.");
            return this._collect();
        }

        // If we have no parameters to confirm, we finish this conversation using finish method of skill.
        debug("Going to perform final action.");
        let finished = new Promise((resolve, reject) => {
            this.skill.finish(this.vp, this.bot_event, this.context, resolve, reject);
        });

        return finished.then(
            (response) => {
                debug("Final action succeeded.");
                // Double check if we have no parameters to confirm since developers can execute collect() method inside finsh().
                if (this.context.to_confirm.length > 0){
                    debug("Going to collect parameter.");
                    return this._collect();
                }

                debug("Final action done. Wrapping up.");
                if (this.skill.clear_context_on_finish && this.context.to_confirm.length == 0){
                    debug(`Clearing context.`);
                    this.context = null;
                }
                return response;
            },
            (response) => {
                debug("Final action failed.");
                return Promise.reject(response);
            }
        );
        /*
        debug("Going to perform final action.");
        return this.skill.finish(this.vp, this.bot_event, this.context).then(
            (response) => {
                // Double check if we have no parameters to confirm since developers can execute collect() method inside finsh().
                if (this.context.to_confirm.length > 0){
                    debug("Going to collect parameter.");
                    return this._collect();
                }

                debug("Final action done. Wrapping up.");
                if (this.skill.clear_context_on_finish && this.context.to_confirm.length == 0){
                    debug(`Clearing context.`);
                    this.context = null;
                }
                return response;
            },
            (response) => {
                return Promise.reject(response);
            }
        );
        */
    }
};
<|endoftext|>


<commit_before>#!/usr/bin/env node

module.exports = (function() {
  var usage = ["Usage: shen-node.js [-noinit] [-c target_file] <files...>"],
      shen = require("./shen"),
      fs = require("fs"),
      stream = require("stream"),
      self = {},
      home = process.env[(process.platform == 'win32')
                         ? 'USERPROFILE' : 'HOME'];

  function main() {
    var i, compile_dest, init = true;
    for (i = 2; i < process.argv.length && process.argv[i].match(/^-/); ++i) {
      switch (process.argv[i]) {
      case "-noinit": init = false; break;
      case "-c": case "-compile": compile_dest = process.argv[++i]; break;
      default: usage_die();
      }
    }
    if (init)
      read_init_file(start);
    else
      start();

    function start() {
      if (i >= process.argv.length)
        shen.start_repl();
      else if (compile_dest)
        compile(process.argv, i, compile_dest);
      else
        on_files(process.argv, i, load);
    }
  }

  function mk_read_byte(stream, vm) {
    var chan = shen.Chan();
    chan.end = -1;
    stream.vm = vm;
    stream.on("error", function(err) {
      chan.write(new Error(err), stream.vm);
    });
    stream.on("data", function(data) {
      var i, vm = stream.vm;
      for (i = 0; i < data.length; ++i)
        chan.write(data[i], vm);
    });
    stream.on("end", function() {
      chan.close(stream.vm);
    });
    return function(vm) {
      stream.vm = vm;
      return chan.read(vm);
    };
  }

  function mk_write_byte(stream) {
    return function(byte, vm) {
      var ret = stream.write(new Buffer([byte]), null, function(err) {
        vm.resume(err ? err : byte);
      });
      vm.interrupt();
    }
  }

  function mk_close(stream) {
    return function() {
      if (stream.end)
        stream.end();
      stream.removeAllListeners();
      stream.on("error", function() {});
    }
  }

  function wrap_stream(stream, dir, vm) {
    switch (dir) {
    case "r":
      return shen.Stream(mk_read_byte(stream, vm), null, mk_close(stream));
    case "w":
      return shen.Stream(null, mk_write_byte(stream), mk_close(stream));
    case "rw": case "wr": case "w+": case "r+":
      return shen.Stream(mk_read_byte(stream, vm), mk_write_byte(stream),
                         mk_close(stream));
    default: throw new Error("Unsupported stream type: " + dir);
    }
  }

  function fix_path(path) {
    return path.replace(/^~/, home);
  }

  function open(name, dir, vm) {
    var path = fix_path(vm.glob["*home-directory*"] + name);
    switch (dir.str) {
    case "in":
      wait_result(fs.createReadStream(path), "readable", function(err) {
        vm.resume((err) ? err : wrap_stream(this, "r", vm));
      });
      break;

    case "out":
      wait_result(fs.createWriteStream(path), "drain", function(err) {
        vm.resume((err) ? err : wrap_stream(this, "w", vm));
      });
      break;

    default: return vm.error("Unsupported 'open' flags");
    }

    function wait_result(stream, ev, onready) {
      stream.once("error", onready);
      stream.once(ev, onready);
      vm.interrupt();
    }
  }

  function io(vm) {
    var io = {};
    io.open = open;
    vm.glob["*stinput*"] = wrap_stream(process.stdin, "r", vm);
    vm.glob["*stoutput*"] = wrap_stream(process.stdout, "w", vm);
    return io;
  }

  function load(src, callback) {
    shen.exec("load", [src], callback);
  }

  function on_files(files, i, fn, callback) {
    if (i < files.length)
      fn(files[i], function() {
        load_files(files, i + 1, callback);
      });
    else if (callback)
      callback();
  }

  function compile(files, i, dest, callback) {
    var list = shen.list(files.slice(i))
    shen.exec("js.save-from-files", [list, dest], callback);
  }

  function read_init_file(callback) {
    var path = home + "/.shen.shen";
    fs.access(path, fs.R_OK, function(err) {
      if (err)
        callback();
      else {
        shen.glob["*hush*"] = true;
        shen.exec("load", [path], function() {
          shen.glob["*hush*"] = false;
          callback();
        });
      }
    });
  }

  function usage_die() {
    process.stdout.write(usage.join(" ") + "\n");
    process.exit(1);
  }

  self.wrap_stream = wrap_stream;
  self.main = main;

  shen.init({io: io, async: true});
  return self;
})();

if (require.main === module)
  module.exports.main();
<commit_msg>[node] Made some stylistic changes.<commit_after>#!/usr/bin/env node

module.exports = (function() {
  var usage = ["Usage: shen-node.js [-noinit] [-c target_file] <files...>"],
      shen = require("./shen"),
      fs = require("fs"),
      stream = require("stream"),
      self = {},
      home = process.env[(process.platform == "win32")
                         ? "USERPROFILE" : "HOME"];

  function main() {
    var i, compile_dest, init = true;
    for (i = 2; i < process.argv.length && process.argv[i].match(/^-/); ++i) {
      switch (process.argv[i]) {
      case "-noinit": init = false; break;
      case "-c": case "-compile": compile_dest = process.argv[++i]; break;
      default: usage_die();
      }
    }
    if (init)
      read_init_file(start);
    else
      start();

    function start() {
      if (i >= process.argv.length)
        shen.start_repl();
      else if (compile_dest)
        compile(process.argv, i, compile_dest);
      else
        on_files(process.argv, i, load);
    }
  }

  function mk_read_byte(stream, vm) {
    var chan = shen.Chan();
    chan.end = -1;
    stream.vm = vm;
    stream.on("error", function(err) {
      chan.write(new Error(err), stream.vm);
    });
    stream.on("data", function(data) {
      var i, vm = stream.vm;
      for (i = 0; i < data.length; ++i)
        chan.write(data[i], vm);
    });
    stream.on("end", function() {
      chan.close(stream.vm);
    });
    return function(vm) {
      stream.vm = vm;
      return chan.read(vm);
    };
  }

  function mk_write_byte(stream) {
    return function(byte, vm) {
      var ret = stream.write(new Buffer([byte]), null, function(err) {
        vm.resume(err ? err : byte);
      });
      vm.interrupt();
    }
  }

  function mk_close(stream) {
    return function() {
      if (stream.end)
        stream.end();
      stream.removeAllListeners();
      stream.on("error", function() {});
    }
  }

  function wrap_stream(stream, dir, vm) {
    switch (dir) {
    case "r":
      return shen.Stream(mk_read_byte(stream, vm), null, mk_close(stream));

    case "w":
      return shen.Stream(null, mk_write_byte(stream), mk_close(stream));

    case "rw": case "wr": case "w+": case "r+":
      return shen.Stream(mk_read_byte(stream, vm), mk_write_byte(stream),
                         mk_close(stream));

    default: throw new Error("Unsupported stream type: " + dir);
    }
  }

  function fix_path(path) {
    return path.replace(/^~/, home);
  }

  function open(name, dir, vm) {
    var path = fix_path(vm.glob["*home-directory*"] + name);
    switch (dir.str) {
    case "in":
      wait_result(fs.createReadStream(path), "readable", function(err) {
        vm.resume((err) ? err : wrap_stream(this, "r", vm));
      });
      break;

    case "out":
      wait_result(fs.createWriteStream(path), "drain", function(err) {
        vm.resume((err) ? err : wrap_stream(this, "w", vm));
      });
      break;

    default: return vm.error("Unsupported 'open' flags");
    }

    function wait_result(stream, ev, onready) {
      stream.once("error", onready);
      stream.once(ev, onready);
      vm.interrupt();
    }
  }

  function io(vm) {
    var io = {};
    io.open = open;
    vm.glob["*stinput*"] = wrap_stream(process.stdin, "r", vm);
    vm.glob["*stoutput*"] = wrap_stream(process.stdout, "w", vm);
    return io;
  }

  function load(src, callback) {
    shen.exec("load", [src], callback);
  }

  function on_files(files, i, fn, callback) {
    if (i < files.length)
      fn(files[i], function() {
        load_files(files, i + 1, callback);
      });
    else if (callback)
      callback();
  }

  function compile(files, i, dest, callback) {
    var list = shen.list(files.slice(i))
    shen.exec("js.save-from-files", [list, dest], callback);
  }

  function read_init_file(callback) {
    var path = home + "/.shen.shen";
    fs.access(path, fs.R_OK, function(err) {
      if (err)
        callback();
      else {
        shen.glob["*hush*"] = true;
        shen.exec("load", [path], function() {
          shen.glob["*hush*"] = false;
          callback();
        });
      }
    });
  }

  function usage_die() {
    process.stdout.write(usage.join(" ") + "\n");
    process.exit(1);
  }

  self.wrap_stream = wrap_stream;
  self.main = main;

  shen.init({io: io, async: true});
  return self;
})();

if (require.main === module)
  module.exports.main();
<|endoftext|>


<commit_before>const ICAL = require( 'ical.js' );
const got = require( 'got' );

const teamData = require( './teamdata' );

const shl_url = "https://calendar.ramses.nu/calendar/66/show/shl.ics";
const ha_url = "https://calendar.ramses.nu/calendar/163/show/schema-19-20.ics";

let games = [];
let lastFetch = false;

const get_games_from_calendar = async function get_games_from_calendar( calendarURL ) {
    const calendarGames = [];
    const response = await got( calendarURL );
    const data = ICAL.parse( response.body );
    const component = new ICAL.Component( data );

    console.log( `Updating games from ${ calendarURL }` );

    component.getAllSubcomponents( 'vevent' ).forEach( ( vevent ) => {
        let summary = vevent.getFirstPropertyValue( 'summary' );
        const event = new ICAL.Component( 'vevent' );

        // Check if the summary contains a number prefixed by a space
        // Probably means it's a score
        if ( /\s\d/.test( summary ) ) {
            let matches = summary.match( /(.+?)\s\d/ );

            summary = matches[ 1 ].trim();
        }

        const [ home, away ] = summary.split( ' - ' );

        if( !home || !away ) {
            console.log( `Unable to parse ${ summary } as home & away, skipping` );

            return true;
        }

        event.addPropertyWithValue( 'dtstamp', ICAL.Time.now() );
        event.addPropertyWithValue( 'summary', `${ home } - ${ away }` );

        event.addPropertyWithValue( 'dtstart', vevent.getFirstPropertyValue( 'dtstart' ) );
        event.addPropertyWithValue( 'dtend', vevent.getFirstPropertyValue( 'dtend' ) );
        event.addPropertyWithValue( 'description', vevent.getFirstPropertyValue( 'description' ) );
        event.addPropertyWithValue( 'location', vevent.getFirstPropertyValue( 'location' ) );
        event.addPropertyWithValue( 'url', vevent.getFirstPropertyValue( 'url' ) );
        event.addPropertyWithValue( 'uid', vevent.getFirstPropertyValue( 'uid' ) );

        const homeData = teamData( home );
        const awayData = teamData( away );

        calendarGames.push( {
            event: event,
            home: homeData.name,
            away: awayData.name,
        } );
    } );

    return calendarGames;
}

const get_games = async () => {
    const haGames = await get_games_from_calendar( ha_url );
    const shlGames = await get_games_from_calendar( shl_url );

    games = haGames.concat( shlGames );
};

const update_games = function() {
    const now = new Date().getTime() / 1000;
    const diff = now - 3600;

    if ( diff < lastFetch ) {
        return Promise.resolve();
    };

    lastFetch = new Date().getTime() / 1000;

    return get_games();
};

const calendar = async (teams) => {
    const filter = [].concat(teams);
    const calendar = new ICAL.Component( 'vcalendar' );
    let include_teams = filter.map((team) => {
        const tempData = teamData( team );

        return tempData.name;
    });

    if (include_teams.length === 0) {
        const allTeams = teamData();

        include_teams = allTeams.map((team) => {
            return team.name;
        });
    }

    calendar.addPropertyWithValue( 'prodid', '-//Hockey McHF//Hockey McHockeyFace//EN' );
    calendar.addPropertyWithValue( 'version', '2.0' );
    calendar.addPropertyWithValue( 'calscale', 'GREGORIAN' );
    calendar.addPropertyWithValue( 'x-wr-timezone', 'Europe/Stockholm' );
    calendar.addPropertyWithValue( 'x-wr-calname', 'Svensk hockey' );
    calendar.addPropertyWithValue( 'x-wr-caldesc', 'Spelschema för svensk hockey' );

    if(filter.length === 1 && filter[0]){
        calendar.updatePropertyWithValue( 'x-wr-calname', include_teams[ 0 ] );
        calendar.updatePropertyWithValue( 'x-wr-caldesc', `Spelschema för ${ include_teams[ 0 ] }` );
    }

    await update_games();

    for (let i = 0; i < games.length; i = i + 1) {
        if ( include_teams.includes( games[ i ].home ) || include_teams.includes( games[ i ].away ) ) {
            calendar.addSubcomponent( games[ i ].event );
        }
    }

    return calendar.toString();
};

module.exports = calendar;
<commit_msg>Don't crash when teams update their names<commit_after>const ICAL = require( 'ical.js' );
const got = require( 'got' );

const teamData = require( './teamdata' );

const shl_url = "https://calendar.ramses.nu/calendar/66/show/shl.ics";
const ha_url = "https://calendar.ramses.nu/calendar/163/show/schema-19-20.ics";

let games = [];
let lastFetch = false;

const get_games_from_calendar = async function get_games_from_calendar( calendarURL ) {
    const calendarGames = [];
    const response = await got( calendarURL );
    const data = ICAL.parse( response.body );
    const component = new ICAL.Component( data );

    console.log( `Updating games from ${ calendarURL }` );

    component.getAllSubcomponents( 'vevent' ).forEach( ( vevent ) => {
        let summary = vevent.getFirstPropertyValue( 'summary' );
        const event = new ICAL.Component( 'vevent' );

        // Check if the summary contains a number prefixed by a space
        // Probably means it's a score
        if ( /\s\d/.test( summary ) ) {
            let matches = summary.match( /(.+?)\s\d/ );

            summary = matches[ 1 ].trim();
        }

        const [ home, away ] = summary.split( ' - ' );

        if( !home || !away ) {
            console.log( `Unable to parse ${ summary } as home & away, skipping` );

            return true;
        }

        event.addPropertyWithValue( 'dtstamp', ICAL.Time.now() );
        event.addPropertyWithValue( 'summary', `${ home } - ${ away }` );

        event.addPropertyWithValue( 'dtstart', vevent.getFirstPropertyValue( 'dtstart' ) );
        event.addPropertyWithValue( 'dtend', vevent.getFirstPropertyValue( 'dtend' ) );
        event.addPropertyWithValue( 'description', vevent.getFirstPropertyValue( 'description' ) );
        event.addPropertyWithValue( 'location', vevent.getFirstPropertyValue( 'location' ) );
        event.addPropertyWithValue( 'url', vevent.getFirstPropertyValue( 'url' ) );
        event.addPropertyWithValue( 'uid', vevent.getFirstPropertyValue( 'uid' ) );

        const homeData = teamData( home );
        const awayData = teamData( away );

        if ( !homeData ) {
            console.error( `Failed to parse ${ home } as a team, skipping` );

            return true;
        }

        if ( !awayData ) {
            console.error( `Failed to parse ${ away } as a team, skipping` );

            return true;
        }

        calendarGames.push( {
            event: event,
            home: homeData.name,
            away: awayData.name,
        } );
    } );

    return calendarGames;
}

const get_games = async () => {
    const haGames = await get_games_from_calendar( ha_url );
    const shlGames = await get_games_from_calendar( shl_url );

    games = haGames.concat( shlGames );
};

const update_games = function() {
    const now = new Date().getTime() / 1000;
    const diff = now - 3600;

    if ( diff < lastFetch ) {
        return Promise.resolve();
    };

    lastFetch = new Date().getTime() / 1000;

    return get_games();
};

const calendar = async (teams) => {
    const filter = [].concat(teams);
    const calendar = new ICAL.Component( 'vcalendar' );
    let include_teams = filter.map((team) => {
        const tempData = teamData( team );

        return tempData.name;
    });

    if (include_teams.length === 0) {
        const allTeams = teamData();

        include_teams = allTeams.map((team) => {
            return team.name;
        });
    }

    calendar.addPropertyWithValue( 'prodid', '-//Hockey McHF//Hockey McHockeyFace//EN' );
    calendar.addPropertyWithValue( 'version', '2.0' );
    calendar.addPropertyWithValue( 'calscale', 'GREGORIAN' );
    calendar.addPropertyWithValue( 'x-wr-timezone', 'Europe/Stockholm' );
    calendar.addPropertyWithValue( 'x-wr-calname', 'Svensk hockey' );
    calendar.addPropertyWithValue( 'x-wr-caldesc', 'Spelschema för svensk hockey' );

    if(filter.length === 1 && filter[0]){
        calendar.updatePropertyWithValue( 'x-wr-calname', include_teams[ 0 ] );
        calendar.updatePropertyWithValue( 'x-wr-caldesc', `Spelschema för ${ include_teams[ 0 ] }` );
    }

    await update_games();

    for (let i = 0; i < games.length; i = i + 1) {
        if ( include_teams.includes( games[ i ].home ) || include_teams.includes( games[ i ].away ) ) {
            calendar.addSubcomponent( games[ i ].event );
        }
    }

    return calendar.toString();
};

module.exports = calendar;
<|endoftext|>