<commit_before>import React from 'react';
import _ from 'underscore';
import InputField from '../input-field';
import classNames from 'classnames';

import DropdownMenu from '../drop-down-menu';
import Tooltip from '../tooltip';
import SectionHeader from '../section-header';
const {DropdownHolder, DropdownElement} = DropdownMenu;

const TableHeader = React.createClass({

    propTypes: {

        //If we want to be able th change between columns, these three values needs to be set.
        allColumnsThatCouldBeRendered: React.PropTypes.array,
        currentColumns: React.PropTypes.array,
        onColumnChange: React.PropTypes.func,

        currentFilters: React.PropTypes.array,

        onSearchChange: React.PropTypes.func,
        useSearch: React.PropTypes.bool,
        searchHint: React.PropTypes.string,

        showJawboneFilter: React.PropTypes.func,

        justifyColumns: React.PropTypes.func,

        // Optional prop to add a BUI Section Header component to the table
        headerLabel: React.PropTypes.oneOfType([
            React.PropTypes.string,
            React.PropTypes.object,
        ]),

        actionHeaderButtons: React.PropTypes.array,

        makeRowsSelectable: React.PropTypes.bool,
        //If we have any selections that should be used when "showSelections" is triggered
        selections: React.PropTypes.array,
        //Function to use on "showSelections" trigger
        onFilter: React.PropTypes.func,
    },

    getDefaultProps() {
        return {
            currentFilters: [],
            selections: [],
            makeRowsSelectable: false,
            actionHeaderButtons: [],
        };
    },

    getInitialState() {
        return {
            displaySearch: false,
        };
    },

    componentDidUpdate() {
        if (this.state.displaySearch) {
            this.refs.tableSearchInputRef.focus();
        }
    },

    _onColumnChange(columnVal) {
        const {allColumnsThatCouldBeRendered, currentColumns, onColumnChange} = this.props;
        let newColumns = currentColumns.slice();
        //Invalid values provided, ignore changes.
        if (typeof onColumnChange !== 'function' || !allColumnsThatCouldBeRendered || !currentColumns) return;

        const column = _(allColumnsThatCouldBeRendered).findWhere({val: columnVal});
        const index = _(newColumns).findIndex((col) => col.val === columnVal);
        if (index !== -1) {
            newColumns.splice(index, 1);
        } else {
            newColumns.push(column);
            let sorting = _(allColumnsThatCouldBeRendered).pluck('val');
            newColumns = _(newColumns).chain()
                .map((item) => {
                    var n = sorting.indexOf(item.val);
                    sorting[n] = '';
                    return [n, item]
                })
                .sort()
                .map((j) => j[1]).value();
        }

        onColumnChange(newColumns, columnVal);
    },

    _displaySearch() {
        this.setState({ displaySearch: true });
    },

    _filterOnSelections() {
        const {currentFilters, selections, onFilter} = this.props;
        const selectionsFilter = _(selections).map((n) => ['_id', n]);
        const currentSelectionsFilters = _(currentFilters).filter((n) => n[0] === '_id');

        if (selections.length && typeof onFilter === 'function') {
            onFilter(
                currentSelectionsFilters.length ? currentSelectionsFilters : selectionsFilter
            );
        }
    },

    _searchBlur(e) {
        if (e.target.value === '') {
            this.setState({ displaySearch: false });
        } else {
            // Do Nothing..
        }
    },

    closeColumnChanger() {
      if (typeof this.refs.dropdownHolder !== 'undefined') {
        this.refs.dropdownHolder.close();
      }
    },

    render() {
        // Tooltips for the header actions
        const tooltipProps = {
            position: 'top',
            space: -10,
            delay: 100,
            style: {
                float: 'left',
            },
        };

        const props = this.props;
        let columnChanger = null;

        //Array of columns which should be active
      let tempCurrentColumnArr = (typeof props.currentColumns[0] === 'string') ? props.currentColumns : props.currentColumns.map(x => x.val);

      if (props.allColumnsThatCouldBeRendered && props.allColumnsThatCouldBeRendered.length && props.currentColumns) {
          const columns = _(props.allColumnsThatCouldBeRendered).map((column, key) => {
              //If column is in the currentColumn arr mark it as active.
              const checked = column.active = tempCurrentColumnArr.indexOf(column.val) > -1;
              return <DropdownElement key={key} checkboxChecked={checked} checkbox label={column.label} onClick={this._onColumnChange.bind(this, column.val)} />
          });

          columnChanger = (

                  <DropdownHolder ref='dropdownHolder' noArrow orientation="right" icon="fa-ellipsis-h table-icon">
                      {columns}
                  </DropdownHolder>

          );

          columnChanger = <Tooltip string='Välj kolumner att visa' {...tooltipProps}>{columnChanger}</Tooltip>;
      }

      const tableSearchClass = classNames('bui-table-search', {
          'display-search': this.state.displaySearch,
      });

      let tableSearch = null;

      if (this.props.useSearch) {
          tableSearch = (

                  <Tooltip string='Snabbsök i tabellen' {...tooltipProps}>

                  </Tooltip>
                  <InputField ref='tableSearchInputRef' icon="fa-search" hint={this.props.searchHint} fastRemove onChange={this.props.onSearchChange} onBlur={this._searchBlur} />

          );
      }

      const filtersWithoutId = _(this.props.currentFilters).filter((n) => n[0] !== '_id');
      //Filter Jawbone
      const filterCount = (filtersWithoutId.length)
                ? {filtersWithoutId.length}
                : null;

      const jawboneParentClass = classNames('bui-table-toggle-filter-button', {
          'bui-inactive-filter-button': !filtersWithoutId.length,
      });

      const jawboneFilter = (

              {filterCount}

      );

      //Selections Icon
      const selectionsParentClass = classNames('bui-table-toggle-filter-button', {
          'bui-inactive-filter-button': !this.props.selections.length,
      });

      const selectionsCount = (this.props.selections.length)
                ? {this.props.selections.length}
                : null;

      let selectionsFilter = null;
      if (this.props.makeRowsSelectable) {
          selectionsFilter = (

                  {selectionsCount}

          );
          selectionsFilter = <Tooltip string='Visa markerade rader' {...tooltipProps}>{selectionsFilter}</Tooltip>;
      }

      // this.props.headerLabel
      let headerLabel;
      if (typeof this.props.headerLabel === 'string') {
          if (this.props.selections.length) {
              if (this.props.selections.length === 1) {
                  headerLabel = Du har markerat {this.props.selections.length} rad;
              } else {
                  headerLabel = Du har markerat {this.props.selections.length} rader;
              }
          } else {
              headerLabel = this.props.headerLabel;
          }
      } else if (_.isObject(this.props.headerLabel)) {
          headerLabel = (
              <SectionHeader
                  icon={this.props.headerLabel.icon}
                  label={this.props.headerLabel.label}
                  desc={this.props.headerLabel.desc}
                  descType={this.props.headerLabel.descType}
                  actionLabel={this.props.headerLabel.actionLabel}
                  onAction={this.props.headerLabel.onAction}
                  style={this.props.headerLabel.style}
                  highlighted={this.props.headerLabel.highlighted}
                  />
          );
      } else {
          headerLabel = '';
      }

    // Override headerLabel with actionHeaderButtons
    if (this.props.actionHeaderButtons && this.props.actionHeaderButtons.length) {
      let i = 0;
      let buttons = [];
      for (let i = 0; i < this.props.actionHeaderButtons.length; i += 1) {
        let v = this.props.actionHeaderButtons[i];
        let b =
{v.label}
;
        buttons.push(b);
      }
      headerLabel =
{buttons}
;
    }

      return (

                  {headerLabel}

                  {tableSearch}
                  <Tooltip string='Visa valda filter' {...tooltipProps}>{jawboneFilter}</Tooltip>
                  <Tooltip string='Återställ kolumnbredder' {...tooltipProps}></Tooltip>
                  {selectionsFilter}
                  {columnChanger}

      )
  }

});

export default TableHeader;
<commit_msg>Moving tooltip for table header icons<commit_after>import React from 'react';
import _ from 'underscore';
import InputField from '../input-field';
import classNames from 'classnames';

import DropdownMenu from '../drop-down-menu';
import Tooltip from '../tooltip';
import SectionHeader from '../section-header';
const {DropdownHolder, DropdownElement} = DropdownMenu;

const TableHeader = React.createClass({

    propTypes: {

        //If we want to be able th change between columns, these three values needs to be set.
        allColumnsThatCouldBeRendered: React.PropTypes.array,
        currentColumns: React.PropTypes.array,
        onColumnChange: React.PropTypes.func,

        currentFilters: React.PropTypes.array,

        onSearchChange: React.PropTypes.func,
        useSearch: React.PropTypes.bool,
        searchHint: React.PropTypes.string,

        showJawboneFilter: React.PropTypes.func,

        justifyColumns: React.PropTypes.func,

        // Optional prop to add a BUI Section Header component to the table
        headerLabel: React.PropTypes.oneOfType([
            React.PropTypes.string,
            React.PropTypes.object,
        ]),

        actionHeaderButtons: React.PropTypes.array,

        makeRowsSelectable: React.PropTypes.bool,
        //If we have any selections that should be used when "showSelections" is triggered
        selections: React.PropTypes.array,
        //Function to use on "showSelections" trigger
        onFilter: React.PropTypes.func,
    },

    getDefaultProps() {
        return {
            currentFilters: [],
            selections: [],
            makeRowsSelectable: false,
            actionHeaderButtons: [],
        };
    },

    getInitialState() {
        return {
            displaySearch: false,
        };
    },

    componentDidUpdate() {
        if (this.state.displaySearch) {
            this.refs.tableSearchInputRef.focus();
        }
    },

    _onColumnChange(columnVal) {
        const {allColumnsThatCouldBeRendered, currentColumns, onColumnChange} = this.props;
        let newColumns = currentColumns.slice();
        //Invalid values provided, ignore changes.
        if (typeof onColumnChange !== 'function' || !allColumnsThatCouldBeRendered || !currentColumns) return;

        const column = _(allColumnsThatCouldBeRendered).findWhere({val: columnVal});
        const index = _(newColumns).findIndex((col) => col.val === columnVal);
        if (index !== -1) {
            newColumns.splice(index, 1);
        } else {
            newColumns.push(column);
            let sorting = _(allColumnsThatCouldBeRendered).pluck('val');
            newColumns = _(newColumns).chain()
                .map((item) => {
                    var n = sorting.indexOf(item.val);
                    sorting[n] = '';
                    return [n, item]
                })
                .sort()
                .map((j) => j[1]).value();
        }

        onColumnChange(newColumns, columnVal);
    },

    _displaySearch() {
        this.setState({ displaySearch: true });
    },

    _filterOnSelections() {
        const {currentFilters, selections, onFilter} = this.props;
        const selectionsFilter = _(selections).map((n) => ['_id', n]);
        const currentSelectionsFilters = _(currentFilters).filter((n) => n[0] === '_id');

        if (selections.length && typeof onFilter === 'function') {
            onFilter(
                currentSelectionsFilters.length ? currentSelectionsFilters : selectionsFilter
            );
        }
    },

    _searchBlur(e) {
        if (e.target.value === '') {
            this.setState({ displaySearch: false });
        } else {
            // Do Nothing..
        }
    },

    closeColumnChanger() {
      if (typeof this.refs.dropdownHolder !== 'undefined') {
        this.refs.dropdownHolder.close();
      }
    },

    render() {
        // Tooltips for the header actions
        const tooltipProps = {
            position: 'left',
            space: 0,
            delay: 100,
            style: {
                float: 'left',
            },
        };

        const props = this.props;
        let columnChanger = null;

        //Array of columns which should be active
      let tempCurrentColumnArr = (typeof props.currentColumns[0] === 'string') ? props.currentColumns : props.currentColumns.map(x => x.val);

      if (props.allColumnsThatCouldBeRendered && props.allColumnsThatCouldBeRendered.length && props.currentColumns) {
          const columns = _(props.allColumnsThatCouldBeRendered).map((column, key) => {
              //If column is in the currentColumn arr mark it as active.
              const checked = column.active = tempCurrentColumnArr.indexOf(column.val) > -1;
              return <DropdownElement key={key} checkboxChecked={checked} checkbox label={column.label} onClick={this._onColumnChange.bind(this, column.val)} />
          });

          columnChanger = (

                  <DropdownHolder ref='dropdownHolder' noArrow orientation="right" icon="fa-ellipsis-h table-icon">
                      {columns}
                  </DropdownHolder>

          );

          columnChanger = <Tooltip string='Välj kolumner att visa' {...tooltipProps}>{columnChanger}</Tooltip>;
      }

      const tableSearchClass = classNames('bui-table-search', {
          'display-search': this.state.displaySearch,
      });

      let tableSearch = null;

      if (this.props.useSearch) {
          tableSearch = (

                  <Tooltip string='Snabbsök i tabellen' {...tooltipProps}>

                  </Tooltip>
                  <InputField ref='tableSearchInputRef' icon="fa-search" hint={this.props.searchHint} fastRemove onChange={this.props.onSearchChange} onBlur={this._searchBlur} />

          );
      }

      const filtersWithoutId = _(this.props.currentFilters).filter((n) => n[0] !== '_id');
      //Filter Jawbone
      const filterCount = (filtersWithoutId.length)
                ? {filtersWithoutId.length}
                : null;

      const jawboneParentClass = classNames('bui-table-toggle-filter-button', {
          'bui-inactive-filter-button': !filtersWithoutId.length,
      });

      const jawboneFilter = (

              {filterCount}

      );

      //Selections Icon
      const selectionsParentClass = classNames('bui-table-toggle-filter-button', {
          'bui-inactive-filter-button': !this.props.selections.length,
      });

      const selectionsCount = (this.props.selections.length)
                ? {this.props.selections.length}
                : null;

      let selectionsFilter = null;
      if (this.props.makeRowsSelectable) {
          selectionsFilter = (

                  {selectionsCount}

          );
          selectionsFilter = <Tooltip string='Visa markerade rader' {...tooltipProps}>{selectionsFilter}</Tooltip>;
      }

      // this.props.headerLabel
      let headerLabel;
      if (typeof this.props.headerLabel === 'string') {
          if (this.props.selections.length) {
              if (this.props.selections.length === 1) {
                  headerLabel = Du har markerat {this.props.selections.length} rad;
              } else {
                  headerLabel = Du har markerat {this.props.selections.length} rader;
              }
          } else {
              headerLabel = this.props.headerLabel;
          }
      } else if (_.isObject(this.props.headerLabel)) {
          headerLabel = (
              <SectionHeader
                  icon={this.props.headerLabel.icon}
                  label={this.props.headerLabel.label}
                  desc={this.props.headerLabel.desc}
                  descType={this.props.headerLabel.descType}
                  actionLabel={this.props.headerLabel.actionLabel}
                  onAction={this.props.headerLabel.onAction}
                  style={this.props.headerLabel.style}
                  highlighted={this.props.headerLabel.highlighted}
                  />
          );
      } else {
          headerLabel = '';
      }

    // Override headerLabel with actionHeaderButtons
    if (this.props.actionHeaderButtons && this.props.actionHeaderButtons.length) {
      let i = 0;
      let buttons = [];
      for (let i = 0; i < this.props.actionHeaderButtons.length; i += 1) {
        let v = this.props.actionHeaderButtons[i];
        let b =
{v.label}
;
        buttons.push(b);
      }
      headerLabel =
{buttons}
;
    }

      return (

                  {headerLabel}

                  {tableSearch}
                  <Tooltip string='Visa valda filter' {...tooltipProps}>{jawboneFilter}</Tooltip>
                  <Tooltip string='Återställ kolumnbredder' {...tooltipProps}></Tooltip>
                  {selectionsFilter}
                  {columnChanger}

      )
  }

});

export default TableHeader;
<|endoftext|>


<commit_before>"use strict";

var Promise = require('bluebird');

var bridgeLib = require("matrix-appservice-bridge");
var Bridge = bridgeLib.Bridge;

var SlackHookHandler = require("./SlackHookHandler");
var BridgedRoom = require("./BridgedRoom");

var AdminCommands = require("./AdminCommands");
var Provisioning = require("./Provisioning");

var randomstring = require("randomstring");

function MatrixSlackBridge(config) {
    var self = this;

    this._config = config;

    this._recentMatrixEventIds = new Array(20);
    this._mostRecentEventIdIdx = 0;

    this._rooms = [];
    this._roomsBySlackChannelId = {};
    this._roomsByMatrixRoomId = {};
    this._roomsByInboundId = {};

    this._bridge = new Bridge({
        homeserverUrl: config.homeserver.url,
        domain: config.homeserver.server_name,
        registration: "slack-registration.yaml",

        controller: {
            onUserQuery: function(queriedUser) {
                return {}; // auto-provision users with no additonal data
            },

            onEvent: function(request, context) {
                var ev = request.getData();
                self.onMatrixEvent(ev);
            },
        }
    });

    this._slackHookHandler = new SlackHookHandler(this);
}

MatrixSlackBridge.prototype.getRoomStore = function() {
    return this._bridge.getRoomStore()
};

MatrixSlackBridge.prototype.putRoomToStore = function(room) {
    var entry = room.toEntry();
    return this.getRoomStore().upsert({id: entry.id}, entry);
};

MatrixSlackBridge.prototype.getUrlForMxc = function(mxc_url) {
    return this._config.homeserver.url + "/_matrix/media/v1/download/" +
        mxc_url.substring("mxc://".length);
};

MatrixSlackBridge.prototype.getTeamToken = function(team_domain) {
    // TODO(paul): move this into a mapping at least, if not full DB
    return this._config["slack_token_" + team_domain];
};

MatrixSlackBridge.prototype.getBotIntent = function() {
    return this._bridge.getIntent();
};

MatrixSlackBridge.prototype.getIntentForSlackUsername = function(slackUser) {
    var username = "@" + this._config.username_prefix + slackUser +
        ":" + this._config.homeserver.server_name;
    return this._bridge.getIntent(username);
};

// Generate a new random inbound ID that is known not to already be in use
MatrixSlackBridge.prototype.genInboundId = function() {
    var attempts = 10;
    while (attempts) {
        var id = randomstring.generate(32);
        if (!(id in this._roomsByInboundId)) return id;

        attempts--;
        if (!attempts) {
            // Prevent tightlooping if randomness goes odd
            throw new Error("Failed to generate a unique inbound ID after 10 attempts");
        }
    }
};

MatrixSlackBridge.prototype.addBridgedRoom = function(room) {
    this._rooms.push(room);

    var id = room.getSlackChannelId();
    if (id) this._roomsBySlackChannelId[id] = room;

    var inbound_id = room.getInboundId();
    if (inbound_id) this._roomsByInboundId[inbound_id] = room;
};

MatrixSlackBridge.prototype.removeBridgedRoom = function(room) {
    var id = room.getSlackChannelId();
    if (id) delete this._roomsBySlackChannelId[id];

    var inbound_id = room.getInboundId();
    if (inbound_id) delete this._roomsByInboundId[inbound_id];

    this._rooms = this._rooms.filter((r) => r !== room);
}

MatrixSlackBridge.prototype.getRoomBySlackChannelId = function(channel_id) {
    return this._roomsBySlackChannelId[channel_id];
};

MatrixSlackBridge.prototype.getRoomBySlackChannelName = function(channel_name) {
    // TODO(paul): this gets inefficient for long lists
    for(var i = 0; i < this._rooms.length; i++) {
        var room = this._rooms[i];
        if (room.getSlackChannelName() === channel_name) {
            return room;
        }
    }

    return null;
};

MatrixSlackBridge.prototype.unlinkRoomFromMatrix = function(room, matrix_room_id) {
    if (!room.removeMatrixRoomId(matrix_room_id)) return Promise.resolve(false);

    delete this._roomsByMatrixRoomId[matrix_room_id];

    var slack_channel_id = room.getSlackChannelId();
    var linkId = matrix_room_id + " " + slack_channel_id;
    return this.getRoomStore().delete({id: linkId}).then(() => {
        return true;
    });
};

MatrixSlackBridge.prototype.getRoomByMatrixRoomId = function(room_id) {
    return this._roomsByMatrixRoomId[room_id];
};

MatrixSlackBridge.prototype.getRoomByInboundId = function(inbound_id) {
    return this._roomsByInboundId[inbound_id];
};

MatrixSlackBridge.prototype.getInboundUrlForRoom = function(room) {
    return this._config.inbound_uri_prefix + room.getInboundId();
};

MatrixSlackBridge.prototype.onMatrixEvent = function(ev) {
    // simple de-dup
    var recents = this._recentMatrixEventIds;
    for (var i = 0; i < recents.length; i++) {
        if (recents[i] != undefined && recents[i] == ev.ev_id) {
          // move the most recent ev to where we found a dup and add the
          // duplicate at the end (reasoning: we only want one of the
          // duplicated ev_id in the list, but we want it at the end)
          recents[i] = recents[this._mostRecentEventIdIdx];
          recents[this._mostRecentEventIdIdx] = ev.ev_id;
          console.log("Ignoring duplicate ev: " + ev.ev_id);
          return;
        }
    }
    this._mostRecentEventIdIdx = (this._mostRecentEventIdIdx + 1) % 20;
    recents[this._mostRecentEventIdIdx] = ev.ev_id;

    var myUserId = this._bridge.getBot().getUserId();

    if (ev.type === "m.room.member" && ev.state_key === myUserId) {
        // A membership event about myself
        var membership = ev.content.membership;
        if (membership === "invite") {
            // Automatically accept all invitations
            this.getBotIntent().join(ev.room_id);
        }

        return;
    }

    if (ev.sender === myUserId) return;
    if (ev.type !== "m.room.message" || !ev.content) return;

    if (this._config.matrix_admin_room && ev.room_id === this._config.matrix_admin_room) {
        this.onMatrixAdminMessage(ev);
        return;
    }

    var room = this.getRoomByMatrixRoomId(ev.room_id);
    if (!room) {
        console.log("Ignoring ev for matrix room with unknown slack channel:" +
            ev.room_id);
        return;
    }
    room.onMatrixMessage(ev);
};

MatrixSlackBridge.prototype.onMatrixAdminMessage = function(ev) {
    var cmd = ev.content.body;

    // Ignore "# comment" lines as chatter between humans sharing the console
    if (cmd.match(/^\s*#/)) return;

    console.log("Admin: " + cmd);

    var response = [];
    function respond(message) {
        if (!response) {
            console.log("Command response too late: " + message);
            return;
        }
        response.push(message);
    };
    // Split the command string into optionally-quoted whitespace-separated
    //   tokens. The quoting preserves whitespace within quoted forms
    // TODO(paul): see if there's a "split like a shell does" function we can use
    //   here instead.
    var args = cmd.match(/(?:[^\s"]+|"[^"]*")+/g);
    cmd = args.shift();

    var p;
    var c = AdminCommands[cmd];
    if (c) {
        p = Promise.try(() => {
            return c.run(this, args, respond);
        }).catch((e) => {
            respond("Command failed: " + e);
        });
    }
    else {
        respond("Unrecognised command: " + cmd);
        p = Promise.resolve();
    }

    p.then(() => {
        if (!response.length) response.push("Done");

        var message = (response.length == 1) ?
            ev.user_id + ": " + response[0] :
            ev.user_id + ":\n" + response.map((s) => "  " + s).join("\n");

        this.getBotIntent().sendText(ev.room_id, message);
        response = null;
    });
};

// This so-called "link" action is really a multi-function generic provisioning
// interface. It will
//  * Create a BridgedRoom instance, linked to the given Matrix room ID
//  * Associate a webhook_uri to an existing instance
MatrixSlackBridge.prototype.actionLink = function(opts) {
    var matrix_room_id = opts.matrix_room_id;

    var room = this.getRoomByMatrixRoomId(matrix_room_id);
    if (room && room.isLegacy()) {
        return Promise.reject("Cannot link to an existing legacy-style room");
    }

    if (!room) {
        var inbound_id = this.genInboundId();

        room = new BridgedRoom(this, {
            inbound_id: inbound_id,
        });
        this.addBridgedRoom(room);

        room.addMatrixRoomId(matrix_room_id);
        this._roomsByMatrixRoomId[matrix_room_id] = room;
    }

    if (opts.slack_webhook_uri) {
        room.updateSlackWebhookUri(opts.slack_webhook_uri);
    }

    if (room.isDirty()) {
        this.putRoomToStore(room);
    }

    return Promise.resolve(room);
};

MatrixSlackBridge.prototype.actionUnlink = function(opts) {
    // For now, until all the legacy-style database entries are gone, we
    // need to support unlinking either new or old kinds of entry.

    var matrix_room_id = opts.matrix_room_id;

    var room = this.getRoomByMatrixRoomId(matrix_room_id);
    if (!room) {
        return Promise.reject("Cannot unlink - unknown channel");
    }

    if (!room.isLegacy()) {
        // New-style - kill it entirely
        this.removeBridgedRoom(room);
        delete this._roomsByMatrixRoomId[matrix_room_id];

        var id = room.toEntry().id;
        return this.getRoomStore().delete({id: id});
    }
    else {
        // Legacy style - remove one Matrix room ID link
        return this.unlinkRoomFromMatrix(room, matrix_room_id).then((success) => {
            if (!success) {
                return Promise.reject("Cannot unlink - not linked to this room");
            }

            if (room.getMatrixRoomIds().length) return Promise.resolve();

            // Room is now entirely empty - remove it from the DB
            this.removeBridgedRoom(room);

            var id = room.toEntry().id;
            return this.getRoomStore().delete({id: id});
        });
    }
};

MatrixSlackBridge.prototype.actionUpgrade = function(opts) {
    var matrix_room_id = opts.matrix_room_id;

    var room = this.getRoomByMatrixRoomId(matrix_room_id);
    if (!room.isLegacy()) {
        return Promise.reject("Cannot upgrade a non-legacy room");
    }

    if (room.getMatrixRoomIds().length != 1) {
        return Promise.reject("Can only upgrade rooms with exactly one Matrix room linked");
    }

    var store = this.getRoomStore();

    var old_id = room.toEntry().id;

    var slack_channel_id = room.getSlackChannelId();
    var old_link_id = matrix_room_id + " " + slack_channel_id;

    var inbound_id = this.genInboundId();
    room.updateInboundId(inbound_id);

    room._upgrade_pending = true;

    return Promise.all([
        this.putRoomToStore(room),
        store.delete({id: old_id}),
        store.delete({id: old_link_id}),
    ]).then(() => {
        this._roomsByInboundId[inbound_id] = room;
        return room;
    });
};

MatrixSlackBridge.prototype.run = function(port) {
    var bridge = this._bridge;
    var config = this._config;

    bridge.loadDatabases().then(() => {
        // Legacy-style BridgedRoom instances
        return this.getRoomStore().select({
            matrix_id: {$exists: false},
        })
    }).then((entries) => {
        entries.forEach((entry) => {
            var room = BridgedRoom.fromEntry(this, entry);
            this.addBridgedRoom(room);
        });
    }).then(() => {
        return this.getRoomStore().select({
            matrix_id: {$exists: true},
        });
    }).then((entries) => {
        entries.forEach((entry) => {
            // These might be links for legacy-style BridgedRooms, or new-style
            // rooms
            // Only way to tell is via the form of the id
            var result = entry.id.match(/^INTEG-(.*)$/);
            if (result) {
                var room = BridgedRoom.fromEntry(this, entry);
                this.addBridgedRoom(room);
                this._roomsByMatrixRoomId[entry.matrix_id] = room;
            }
            else {
                var room = this.getRoomBySlackChannelId(entry.remote_id);
                if (room) {
                    room.addMatrixRoomId(entry.matrix_id);
                    this._roomsByMatrixRoomId[entry.matrix_id] = room;
                }
            }
        });
    });

    this._slackHookHandler.startAndListen(
        config.slack_hook_port, config.tls
    ).then(() => {
        bridge.run(port, config);
        Provisioning.addAppServicePath(bridge, this);
    });
}

// Code below is the "provisioning"; the commands available over the
// Provisioning API

Provisioning.commands.getbotid = new Provisioning.Command({
    params: [],
    func: function(bridge, req, res) {
        res.json({bot_user_id: bridge._bridge.getBot().getUserId()});
    }
});

Provisioning.commands.getlink = new Provisioning.Command({
    params: ["matrix_room_id"],
    func: function(bridge, req, res, matrix_room_id) {
        var room = bridge.getRoomByMatrixRoomId(matrix_room_id);
        if (!room) {
            res.status(404).json({error: "Link not found"});
            return;
        }

        // Convert the room 'status' into a scalar 'status'
        var status = room.getStatus();
        if (status === "ready") {
            // OK
        }
        else if(status === "pending-params") {
            status = "partial";
        }
        else {
            status = "unknown";
        }

        res.json({
            status: status,
            slack_channel_name: room.getSlackChannelName(),
            slack_webhook_uri: room.getSlackWebhookUri(),
            // This is slightly a lie
            matrix_room_id: matrix_room_id,
            inbound_uri: bridge.getInboundUrlForRoom(room),
        });
    }
});

Provisioning.commands.link = new Provisioning.Command({
    params: ["matrix_room_id"],
    func: function(bridge, req, res, matrix_room_id) {
        var params = req.body;
        var opts = {
            matrix_room_id: matrix_room_id,
        };

        opts.slack_webhook_uri = params.slack_webhook_uri;

        bridge.actionLink(opts).then(
            ()    => { res.json({}); },
            (err) => { res.status(500).json({error: err}); }
        );
    }
});

Provisioning.commands.unlink = new Provisioning.Command({
    params: ["matrix_room_id"],
    func: function(bridge, req, res, matrix_room_id) {
        var params = req.body;
        var opts = {
            matrix_room_id: matrix_room_id,
        };

        if (params.slack_channel_id) {
            opts.slack_channel_id = params.slack_channel_id;
        }
        else if (params.slack_channel_name) {
            opts.slack_channel_name = params.slack_channel_name;
        }
        else {
            res.status(400).json({error: "Require either slack_channel_id or slack_channel_name"});
            return;
        }

        bridge.actionUnlink(opts).then(
            ()    => { res.json({}); },
            (err) => { res.status(500).json({error: err}); }
        );
    }
});

module.exports = MatrixSlackBridge;
<commit_msg>Bugfix to legacy room link deleting code<commit_after>"use strict";

var Promise = require('bluebird');

var bridgeLib = require("matrix-appservice-bridge");
var Bridge = bridgeLib.Bridge;

var SlackHookHandler = require("./SlackHookHandler");
var BridgedRoom = require("./BridgedRoom");

var AdminCommands = require("./AdminCommands");
var Provisioning = require("./Provisioning");

var randomstring = require("randomstring");

function MatrixSlackBridge(config) {
    var self = this;

    this._config = config;

    this._recentMatrixEventIds = new Array(20);
    this._mostRecentEventIdIdx = 0;

    this._rooms = [];
    this._roomsBySlackChannelId = {};
    this._roomsByMatrixRoomId = {};
    this._roomsByInboundId = {};

    this._bridge = new Bridge({
        homeserverUrl: config.homeserver.url,
        domain: config.homeserver.server_name,
        registration: "slack-registration.yaml",

        controller: {
            onUserQuery: function(queriedUser) {
                return {}; // auto-provision users with no additonal data
            },

            onEvent: function(request, context) {
                var ev = request.getData();
                self.onMatrixEvent(ev);
            },
        }
    });

    this._slackHookHandler = new SlackHookHandler(this);
}

MatrixSlackBridge.prototype.getRoomStore = function() {
    return this._bridge.getRoomStore()
};

MatrixSlackBridge.prototype.putRoomToStore = function(room) {
    var entry = room.toEntry();
    return this.getRoomStore().upsert({id: entry.id}, entry);
};

MatrixSlackBridge.prototype.getUrlForMxc = function(mxc_url) {
    return this._config.homeserver.url + "/_matrix/media/v1/download/" +
        mxc_url.substring("mxc://".length);
};

MatrixSlackBridge.prototype.getTeamToken = function(team_domain) {
    // TODO(paul): move this into a mapping at least, if not full DB
    return this._config["slack_token_" + team_domain];
};

MatrixSlackBridge.prototype.getBotIntent = function() {
    return this._bridge.getIntent();
};

MatrixSlackBridge.prototype.getIntentForSlackUsername = function(slackUser) {
    var username = "@" + this._config.username_prefix + slackUser +
        ":" + this._config.homeserver.server_name;
    return this._bridge.getIntent(username);
};

// Generate a new random inbound ID that is known not to already be in use
MatrixSlackBridge.prototype.genInboundId = function() {
    var attempts = 10;
    while (attempts) {
        var id = randomstring.generate(32);
        if (!(id in this._roomsByInboundId)) return id;

        attempts--;
        if (!attempts) {
            // Prevent tightlooping if randomness goes odd
            throw new Error("Failed to generate a unique inbound ID after 10 attempts");
        }
    }
};

MatrixSlackBridge.prototype.addBridgedRoom = function(room) {
    this._rooms.push(room);

    var id = room.getSlackChannelId();
    if (id) this._roomsBySlackChannelId[id] = room;

    var inbound_id = room.getInboundId();
    if (inbound_id) this._roomsByInboundId[inbound_id] = room;
};

MatrixSlackBridge.prototype.removeBridgedRoom = function(room) {
    var id = room.getSlackChannelId();
    if (id) delete this._roomsBySlackChannelId[id];

    var inbound_id = room.getInboundId();
    if (inbound_id) delete this._roomsByInboundId[inbound_id];

    this._rooms = this._rooms.filter((r) => r !== room);
}

MatrixSlackBridge.prototype.getRoomBySlackChannelId = function(channel_id) {
    return this._roomsBySlackChannelId[channel_id];
};

MatrixSlackBridge.prototype.getRoomBySlackChannelName = function(channel_name) {
    // TODO(paul): this gets inefficient for long lists
    for(var i = 0; i < this._rooms.length; i++) {
        var room = this._rooms[i];
        if (room.getSlackChannelName() === channel_name) {
            return room;
        }
    }

    return null;
};

MatrixSlackBridge.prototype.unlinkRoomFromMatrix = function(room, matrix_room_id) {
    if (!room.removeMatrixRoomId(matrix_room_id)) return Promise.resolve(false);

    delete this._roomsByMatrixRoomId[matrix_room_id];

    var slack_channel_id = room.getSlackChannelId();
    var linkId = matrix_room_id + " " + slack_channel_id;
    return this.getRoomStore().delete({id: linkId}).then(() => {
        return true;
    });
};

MatrixSlackBridge.prototype.getRoomByMatrixRoomId = function(room_id) {
    return this._roomsByMatrixRoomId[room_id];
};

MatrixSlackBridge.prototype.getRoomByInboundId = function(inbound_id) {
    return this._roomsByInboundId[inbound_id];
};

MatrixSlackBridge.prototype.getInboundUrlForRoom = function(room) {
    return this._config.inbound_uri_prefix + room.getInboundId();
};

MatrixSlackBridge.prototype.onMatrixEvent = function(ev) {
    // simple de-dup
    var recents = this._recentMatrixEventIds;
    for (var i = 0; i < recents.length; i++) {
        if (recents[i] != undefined && recents[i] == ev.ev_id) {
          // move the most recent ev to where we found a dup and add the
          // duplicate at the end (reasoning: we only want one of the
          // duplicated ev_id in the list, but we want it at the end)
          recents[i] = recents[this._mostRecentEventIdIdx];
          recents[this._mostRecentEventIdIdx] = ev.ev_id;
          console.log("Ignoring duplicate ev: " + ev.ev_id);
          return;
        }
    }
    this._mostRecentEventIdIdx = (this._mostRecentEventIdIdx + 1) % 20;
    recents[this._mostRecentEventIdIdx] = ev.ev_id;

    var myUserId = this._bridge.getBot().getUserId();

    if (ev.type === "m.room.member" && ev.state_key === myUserId) {
        // A membership event about myself
        var membership = ev.content.membership;
        if (membership === "invite") {
            // Automatically accept all invitations
            this.getBotIntent().join(ev.room_id);
        }

        return;
    }

    if (ev.sender === myUserId) return;
    if (ev.type !== "m.room.message" || !ev.content) return;

    if (this._config.matrix_admin_room && ev.room_id === this._config.matrix_admin_room) {
        this.onMatrixAdminMessage(ev);
        return;
    }

    var room = this.getRoomByMatrixRoomId(ev.room_id);
    if (!room) {
        console.log("Ignoring ev for matrix room with unknown slack channel:" +
            ev.room_id);
        return;
    }
    room.onMatrixMessage(ev);
};

MatrixSlackBridge.prototype.onMatrixAdminMessage = function(ev) {
    var cmd = ev.content.body;

    // Ignore "# comment" lines as chatter between humans sharing the console
    if (cmd.match(/^\s*#/)) return;

    console.log("Admin: " + cmd);

    var response = [];
    function respond(message) {
        if (!response) {
            console.log("Command response too late: " + message);
            return;
        }
        response.push(message);
    };
    // Split the command string into optionally-quoted whitespace-separated
    //   tokens. The quoting preserves whitespace within quoted forms
    // TODO(paul): see if there's a "split like a shell does" function we can use
    //   here instead.
    var args = cmd.match(/(?:[^\s"]+|"[^"]*")+/g);
    cmd = args.shift();

    var p;
    var c = AdminCommands[cmd];
    if (c) {
        p = Promise.try(() => {
            return c.run(this, args, respond);
        }).catch((e) => {
            respond("Command failed: " + e);
        });
    }
    else {
        respond("Unrecognised command: " + cmd);
        p = Promise.resolve();
    }

    p.then(() => {
        if (!response.length) response.push("Done");

        var message = (response.length == 1) ?
            ev.user_id + ": " + response[0] :
            ev.user_id + ":\n" + response.map((s) => "  " + s).join("\n");

        this.getBotIntent().sendText(ev.room_id, message);
        response = null;
    });
};

// This so-called "link" action is really a multi-function generic provisioning
// interface. It will
//  * Create a BridgedRoom instance, linked to the given Matrix room ID
//  * Associate a webhook_uri to an existing instance
MatrixSlackBridge.prototype.actionLink = function(opts) {
    var matrix_room_id = opts.matrix_room_id;

    var room = this.getRoomByMatrixRoomId(matrix_room_id);
    if (room && room.isLegacy()) {
        return Promise.reject("Cannot link to an existing legacy-style room");
    }

    if (!room) {
        var inbound_id = this.genInboundId();

        room = new BridgedRoom(this, {
            inbound_id: inbound_id,
        });
        this.addBridgedRoom(room);

        room.addMatrixRoomId(matrix_room_id);
        this._roomsByMatrixRoomId[matrix_room_id] = room;
    }

    if (opts.slack_webhook_uri) {
        room.updateSlackWebhookUri(opts.slack_webhook_uri);
    }

    if (room.isDirty()) {
        this.putRoomToStore(room);
    }

    return Promise.resolve(room);
};

MatrixSlackBridge.prototype.actionUnlink = function(opts) {
    // For now, until all the legacy-style database entries are gone, we
    // need to support unlinking either new or old kinds of entry.

    var matrix_room_id = opts.matrix_room_id;

    var room = this.getRoomByMatrixRoomId(matrix_room_id);
    if (!room) {
        return Promise.reject("Cannot unlink - unknown channel");
    }

    if (!room.isLegacy()) {
        // New-style - kill it entirely
        this.removeBridgedRoom(room);
        delete this._roomsByMatrixRoomId[matrix_room_id];

        var id = room.toEntry().id;
        return this.getRoomStore().delete({id: id});
    }
    else {
        // Legacy style - remove one Matrix room ID link
        return this.unlinkRoomFromMatrix(room, matrix_room_id).then((success) => {
            if (!success) {
                return Promise.reject("Cannot unlink - not linked to this room");
            }

            if (room.getMatrixRoomIds().length) return Promise.resolve();

            // Room is now entirely empty - remove it from the DB
            this.removeBridgedRoom(room);

            var id = room.toEntry().id;
            return this.getRoomStore().delete({id: id});
        });
    }
};

MatrixSlackBridge.prototype.actionUpgrade = function(opts) {
    var matrix_room_id = opts.matrix_room_id;

    var room = this.getRoomByMatrixRoomId(matrix_room_id);
    if (!room.isLegacy()) {
        return Promise.reject("Cannot upgrade a non-legacy room");
    }

    if (room.getMatrixRoomIds().length != 1) {
        return Promise.reject("Can only upgrade rooms with exactly one Matrix room linked");
    }

    var store = this.getRoomStore();

    var old_id = room.toEntry().id;

    var slack_channel_id = room.getSlackChannelId();
    var old_link_id = slack_channel_id + " " + matrix_room_id;

    var inbound_id = this.genInboundId();
    room.updateInboundId(inbound_id);

    room._upgrade_pending = true;

    return Promise.all([
        this.putRoomToStore(room),
        store.delete({id: old_id}),
        store.delete({id: old_link_id}),
    ]).then(() => {
        this._roomsByInboundId[inbound_id] = room;
        return room;
    });
};

MatrixSlackBridge.prototype.run = function(port) {
    var bridge = this._bridge;
    var config = this._config;

    bridge.loadDatabases().then(() => {
        // Legacy-style BridgedRoom instances
        return this.getRoomStore().select({
            matrix_id: {$exists: false},
        })
    }).then((entries) => {
        entries.forEach((entry) => {
            var room = BridgedRoom.fromEntry(this, entry);
            this.addBridgedRoom(room);
        });
    }).then(() => {
        return this.getRoomStore().select({
            matrix_id: {$exists: true},
        });
    }).then((entries) => {
        entries.forEach((entry) => {
            // These might be links for legacy-style BridgedRooms, or new-style
            // rooms
            // Only way to tell is via the form of the id
            var result = entry.id.match(/^INTEG-(.*)$/);
            if (result) {
                var room = BridgedRoom.fromEntry(this, entry);
                this.addBridgedRoom(room);
                this._roomsByMatrixRoomId[entry.matrix_id] = room;
            }
            else {
                var room = this.getRoomBySlackChannelId(entry.remote_id);
                if (room) {
                    room.addMatrixRoomId(entry.matrix_id);
                    this._roomsByMatrixRoomId[entry.matrix_id] = room;
                }
            }
        });
    });

    this._slackHookHandler.startAndListen(
        config.slack_hook_port, config.tls
    ).then(() => {
        bridge.run(port, config);
        Provisioning.addAppServicePath(bridge, this);
    });
}

// Code below is the "provisioning"; the commands available over the
// Provisioning API

Provisioning.commands.getbotid = new Provisioning.Command({
    params: [],
    func: function(bridge, req, res) {
        res.json({bot_user_id: bridge._bridge.getBot().getUserId()});
    }
});

Provisioning.commands.getlink = new Provisioning.Command({
    params: ["matrix_room_id"],
    func: function(bridge, req, res, matrix_room_id) {
        var room = bridge.getRoomByMatrixRoomId(matrix_room_id);
        if (!room) {
            res.status(404).json({error: "Link not found"});
            return;
        }

        // Convert the room 'status' into a scalar 'status'
        var status = room.getStatus();
        if (status === "ready") {
            // OK
        }
        else if(status === "pending-params") {
            status = "partial";
        }
        else {
            status = "unknown";
        }

        res.json({
            status: status,
            slack_channel_name: room.getSlackChannelName(),
            slack_webhook_uri: room.getSlackWebhookUri(),
            // This is slightly a lie
            matrix_room_id: matrix_room_id,
            inbound_uri: bridge.getInboundUrlForRoom(room),
        });
    }
});

Provisioning.commands.link = new Provisioning.Command({
    params: ["matrix_room_id"],
    func: function(bridge, req, res, matrix_room_id) {
        var params = req.body;
        var opts = {
            matrix_room_id: matrix_room_id,
        };

        opts.slack_webhook_uri = params.slack_webhook_uri;

        bridge.actionLink(opts).then(
            ()    => { res.json({}); },
            (err) => { res.status(500).json({error: err}); }
        );
    }
});

Provisioning.commands.unlink = new Provisioning.Command({
    params: ["matrix_room_id"],
    func: function(bridge, req, res, matrix_room_id) {
        var params = req.body;
        var opts = {
            matrix_room_id: matrix_room_id,
        };

        if (params.slack_channel_id) {
            opts.slack_channel_id = params.slack_channel_id;
        }
        else if (params.slack_channel_name) {
            opts.slack_channel_name = params.slack_channel_name;
        }
        else {
            res.status(400).json({error: "Require either slack_channel_id or slack_channel_name"});
            return;
        }

        bridge.actionUnlink(opts).then(
            ()    => { res.json({}); },
            (err) => { res.status(500).json({error: err}); }
        );
    }
});

module.exports = MatrixSlackBridge;
<|endoftext|>


<commit_before>/*
	Copyright (C) 2007-2017 Claude SIMON (http://q37.info/contact/).

	This file is part of eSketch.

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

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

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

"use strict"

var affix = "esketch";

// Begin of generic part.
var njsq = null;
var componentPath = null;
var componentFilename = null;
var path = require("path");

if (process.env.EPEIOS_SRC) {
    if (process.platform == 'win32')
        componentPath = 'h:/bin/';
    else
        componentPath = '~/bin/';
    njsq = require(componentPath + 'njsq.node');
} else {
    njsq = require('njsq');
    componentPath = __dirname;
}

componentFilename = path.join(componentPath, affix + "njs").replace(/\\/g, "\\\\").replace(/'/g, "\\'");
njsq.register(componentFilename);
module.exports = njsq;
// End of generic part.

module.exports.returnArgument = (text) => { return njsq._wrapper(0, text) };

<commit_msg>06/08/2017 12:26:14<commit_after>/*
	Copyright (C) 2007-2017 Claude SIMON (http://q37.info/contact/).

	This file is part of eSketch.

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

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

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

"use strict"

var affix = "esketch";

// Begin of generic part.
var njsq = null;
var componentPath = null;
var componentFilename = null;
var path = require("path");

if (process.env.EPEIOS_SRC) {
    if (process.platform == 'win32')
        componentPath = 'h:/bin/';
    else
        componentPath = '~/bin/';
    njsq = require(componentPath + 'njsq.node');
} else {
    njsq = require('NJSq');
    componentPath = __dirname;
}

componentFilename = path.join(componentPath, affix + "njs").replace(/\\/g, "\\\\").replace(/'/g, "\\'");
njsq.register(componentFilename);
module.exports = njsq;
// End of generic part.

module.exports.returnArgument = (text) => { return njsq._wrapper(0, text) };

<|endoftext|>


<commit_before>'use strict'

class SegmentTable {
  constructor () {
    this.segments = {};
  }
  addSegment(code, position, required, repetition) {
    this.segments[code] = [position, required, repetition];
  }
};

/**
 * The base Parser class encapsulates an online parsing algorithm which can be
 * extended through the use of user-defined hooks.
 */
class Parser {
  constructor() {
    this._segmentHooks = {};
    this._hooks = [];
    this._state = {};
    this._segments = {};
    this._elements = {};
  }
  /**
   * @summary Reset the parser to it's initial state.
   */
  reset() {
    this._index = 0;
    this._state.mode = 'segment';
    this._state.elements = [];
    this._state.components = [];
  }
  /**
   * @summary Request a state variable.
   * @param key The name of the state variable.
   */
  state(key, value) {
    if (value) {
      this._state[key] = value;
    } else {
      return this._state[key];
    }
  }
  /**
   * @summary Add a hook to the parser.
   * @param {Function} hook The hook to be called after a segment is completed.
   * @param {String} segment The segment triggering this hook.
   *
   * If no segment is provided, the hook will be called after each segment read
   * by the parser.
   */
  hook(hook, segment) {
    if (segment) {
      this._segmentHooks[segment] = this._segmentHooks[segment] || [];
      this._segmentHooks[segment].push(hook);
    } else {
      this._hooks.push(hook);
    }
  }
  /**
   * @summary Define segment and element structures.
   * @param {Object} definitions An object containing the definitions.
   * @param options Set options like the overriding policy.
   *
   * By default the parser accepts any wellformed input if it doesn't find
   * definitinons for the current segment or element being parsed. Additionally
   * the parser can be forced to check the number and format of the elements and
   * components if the right definitions are included.
   *
   * To define a segment or element the definitions object should contain the
   * name as a key, and an object describing it's structure as a value. This
   * object contains the `requires` key to define the number of mandatory
   * elements or components. The key `elements` should be included containing a
   * list of element names to describe a segment. Similarly, an element
   * definition contains a `components` array describing the format of the
   * components.
   *
   * To simplify things, a non-composite element is regarded as an element
   * having only one component.
   */
  define(definitions, options) {
    options = options || {};

    for (let key in definitions) {
      if (definitions[key].elements && key.length === 3) {
        this._segments[key] = definitions[key];
      }
      if (definitions[key].components && key.length === 4) {
        this._elements[key] = definitions[key];
      }
    }
  }
  /**
   * @summary
   * @param {String} input The component string to parse.
   * @param {String} format The format string to parse the input string.
   * @param {Object} options Options to use (like a custom decimal mark).
   *
   * This method will parse an input string using a format string. If succesful
   * it will return the input using an appropriate type, otherwise it will throw
   * an error.
   */
  component(input, format, options) {
    options = options || {};
    let decimal_mark = options.decimal_mark || '.';

    let match;
    let parts;
    if (parts = /^(a|an|n)(\.\.)?([1-9][0-9]*)?$/.exec(format)) {
      let maximum = parseInt(parts[3]);
      let minimum = parts[2] === '..' ? 0 : maximum;
      if (parts[1] === 'a') {
        if (match = /^[A-Za-z.,\-()/= ]*$/.exec(input)) {
          if (match[0].length >= minimum && match[0].length <= maximum) {
            return match[0];
          }
        }
      } else if (parts[1] === 'an') {
        if (match = /^[0-9A-Za-z.,\-()/= ]*$/.exec(input)) {
          if (match[0].length >= minimum && match[0].length <= maximum) {
            return match[0];
          }
        }
      } else if (parts[1] === 'n') {
        let length;
        if (match = /^([0-9]*)\.?([0-9]*)$/.exec(input)) {
          length = match[1].length + match[2].length;
          if (length >= minimum && length <= maximum) {
            return parseFloat(match[1] + '.' + match[2]);
          }
        }
      }
      throw new Error('Couldn\'t parse ' + input + ' with format string ' + format);
    } else {
      throw new Error('Couldn\'t understand the format string ' + format);
    }
  }
  /**
   * @summary Parse a UN/EDIFACT document.
   * @param {String} input The input document.
   * @param {Object} options Any options to use.
   *
   * This parser behaves similar to a SAX parser. It parses the document but by
   * itself does not construct any output. To get the parser to do anything
   * useful, user-defined hooks should be provided.
   */
  parse(input, options) {
    let match;

    this.reset();
    options = options || {};

    // Read any EDIFACT special characters from options if provided.
    let component_data_separator = options.component_data_separator || ':';
    let data_element_separator = options.data_element_separator || '+';
    let decimal_mark = options.decimal_mark || '.';
    let release_character = options.release_character || '?';
    let segment_terminator = options.segment_terminator || '\'';

    // Match the UNA header if present.
    let una = /UNA(.)(.)(.)(.)\ (.)/g;
    if (match = una.exec(input)) {
      component_data_separator = match[1];
      data_element_separator = match[2];
      decimal_mark = match[3];
      release_character = match[4];
      segment_terminator = match[5];
      this._index = una.lastIndex;
    }

    // Construct a finite state machine describing the states of the parser.
    let machine = {};
    machine['segment'] = {};
    machine['element'] = {};
    machine['component'] = {};
    machine['wrap'] = {};
    machine['segment'][data_element_separator] = 'element';
    machine['segment'][segment_terminator] = 'wrap';
    machine['segment']['\n'] = 'segment';
    machine['segment']['\r'] = 'segment';
    machine['element'][data_element_separator] = 'element';
    machine['element'][component_data_separator] = 'component';
    machine['element'][segment_terminator] = 'wrap';
    machine['component'][data_element_separator] = 'element';
    machine['component'][component_data_separator] = 'component';
    machine['component'][segment_terminator] = 'wrap';
    machine['wrap']['\n'] = 'segment';
    machine['wrap']['\r'] = 'segment';

    // Construct a regex to match tokens from the EDIFACT message.
    let separator = (component_data_separator + '|' + data_element_separator + '|' + segment_terminator + '|\n|\r').replace(/([+?*(){}\[\]])/g, '\\$1');
    let escape = release_character.replace(/([+?*(){}\[\]])/g, '\\$1');
    // A regex with reluctant matchers.
    let expression = '((?:(?:' + escape + ')?.)*?)(' + separator + ')';
    // A regex with greedy matchers.
    //let expression = '((?:' + escape + '.|(?!' + separator + ').)*)(' + separator + ')';
    // A regex based on negative lookaround.
    //let expression = '(.*?)((?<!' + escape + ')' + separator + ')';
    let regex = new RegExp(expression, 'g');
    regex.lastIndex = una.lastIndex;

    // Start reading tokens from the input.
    while (match = regex.exec(input)) {
      let value = match[1];
      let transition = match[2];
      let definition;
      if (this._index !== match.index) {
        throw Error('Unmatched sequence at index ' + this._index + ': ' + value);
      }
      switch (this._state.mode) {
        case 'segment':
        this._state.segment = value;
        this._state.elements = [];
        this._state.element = undefined;
        break;
        case 'element':
        definition = this._segments[this._state.segment];
        if (definition) {
          if (this._state.elements.length < definition.elements.length) {
            this._state.element = definition.elements[this._state.elements.length];
          } else {
            throw new Error('Maximum number of elements exceeded for segment ' + this._state.segment);
          }
        }
        this._state.components = this._state.elements[this._state.elements.push([]) - 1];
        case 'component':
        definition = this._elements[this._state.element];
        if (definition) {
          if (this._state.components.length < definition.components.length) {
            let format = definition.components[this._state.components.length];
            value = this.component(value, format);
          } else {
            throw new Error('Maximum number of components exceeded for element ' + this._state.element + ' in segment ' + this._state.segment);
          }
        }
        this._state.components.push(value);
        break;
      }
      this._state.mode = machine[this._state.mode][transition];
      if (this._state.mode === undefined) {
        throw Error('Invalid character ' + transition + ' after reading: ' + value);
      }
      switch (this._state.mode) {
        case 'wrap':
        definition = this._segments[this._state.segment];
        if (definition && this._state.elements.length < this._segments[this._state.segment].requires) {
          throw new Error('Segment ' + this._state.segment + ' requires at least ' + this._segments[this._state.segment].requires + ' elements');
        }
        case 'element':
        definition = this._elements[this._state.element];
        if (definition && this._state.components.length < this._elements[this._state.element].requires) {
          throw new Error('Element ' + this._state.element + ' in segment ' + this._state.segment + ' requires at least ' + this._elements[this._state.element].requires + ' components');
        }
        break;
      }
      switch (this._state.mode) {
        case 'wrap':
        for (let i = 0; i < this._hooks.length; i++) {
          this._hooks[i](this);
        }
        if (this._segmentHooks[this._state.segment]) {
          for (let i = 0; i < this._segmentHooks[this._state.segment].length; i++) {
            this._segmentHooks[this._state.segment][i](this);
          }
        }
        break;
      }
      this._index = regex.lastIndex;
    }
    if (this._state.mode !== 'wrap' && this._state.mode !== 'segment') {
      throw new Error('Encountered an unterminated segment at the end of the input');
    }
    if (this._index !== input.length) {
      throw new Error('Invalid input encountered at index ' + regex.lastIndex);
    }
  }
};

/**
 * The Reader class extends Parser. It adds a default hook which constructs a
 * javascript array representing the original document.
 */
class Reader extends Parser {
  constructor() {
    super();
    this.hook(Reader.addSegment);
  }
  reset() {
    super.reset();
    this.state('result', []);
  }
  /**
   * @summary Parse the input document and return it as a javascript array.
   * @param {String} input The input document.
   * @param {Object} options Any options to use.
   */
  parse(document, options) {
    super.parse(document, options);
    return this.state('result');
  }
  static addSegment(parser) {
    let segment = {};
    segment.name = parser.state('segment');
    segment.elements = parser.state('elements');
    parser.state('result').push(segment);
  }
};

module.exports.Parser = Parser;
module.exports.Reader = Reader;
<commit_msg>Accept messages without newlines.<commit_after>'use strict'

class SegmentTable {
  constructor () {
    this.segments = {};
  }
  addSegment(code, position, required, repetition) {
    this.segments[code] = [position, required, repetition];
  }
};

/**
 * The base Parser class encapsulates an online parsing algorithm which can be
 * extended through the use of user-defined hooks.
 */
class Parser {
  constructor() {
    this._segmentHooks = {};
    this._hooks = [];
    this._state = {};
    this._segments = {};
    this._elements = {};
  }
  /**
   * @summary Reset the parser to it's initial state.
   */
  reset() {
    this._index = 0;
    this._state.mode = 'segment';
    this._state.elements = [];
    this._state.components = [];
  }
  /**
   * @summary Request a state variable.
   * @param key The name of the state variable.
   */
  state(key, value) {
    if (value) {
      this._state[key] = value;
    } else {
      return this._state[key];
    }
  }
  /**
   * @summary Add a hook to the parser.
   * @param {Function} hook The hook to be called after a segment is completed.
   * @param {String} segment The segment triggering this hook.
   *
   * If no segment is provided, the hook will be called after each segment read
   * by the parser.
   */
  hook(hook, segment) {
    if (segment) {
      this._segmentHooks[segment] = this._segmentHooks[segment] || [];
      this._segmentHooks[segment].push(hook);
    } else {
      this._hooks.push(hook);
    }
  }
  /**
   * @summary Define segment and element structures.
   * @param {Object} definitions An object containing the definitions.
   * @param options Set options like the overriding policy.
   *
   * By default the parser accepts any wellformed input if it doesn't find
   * definitinons for the current segment or element being parsed. Additionally
   * the parser can be forced to check the number and format of the elements and
   * components if the right definitions are included.
   *
   * To define a segment or element the definitions object should contain the
   * name as a key, and an object describing it's structure as a value. This
   * object contains the `requires` key to define the number of mandatory
   * elements or components. The key `elements` should be included containing a
   * list of element names to describe a segment. Similarly, an element
   * definition contains a `components` array describing the format of the
   * components.
   *
   * To simplify things, a non-composite element is regarded as an element
   * having only one component.
   */
  define(definitions, options) {
    options = options || {};

    for (let key in definitions) {
      if (definitions[key].elements && key.length === 3) {
        this._segments[key] = definitions[key];
      }
      if (definitions[key].components && key.length === 4) {
        this._elements[key] = definitions[key];
      }
    }
  }
  /**
   * @summary
   * @param {String} input The component string to parse.
   * @param {String} format The format string to parse the input string.
   * @param {Object} options Options to use (like a custom decimal mark).
   *
   * This method will parse an input string using a format string. If succesful
   * it will return the input using an appropriate type, otherwise it will throw
   * an error.
   */
  component(input, format, options) {
    options = options || {};
    let decimal_mark = options.decimal_mark || '.';

    let match;
    let parts;
    if (parts = /^(a|an|n)(\.\.)?([1-9][0-9]*)?$/.exec(format)) {
      let maximum = parseInt(parts[3]);
      let minimum = parts[2] === '..' ? 0 : maximum;
      if (parts[1] === 'a') {
        if (match = /^[A-Za-z.,\-()/= ]*$/.exec(input)) {
          if (match[0].length >= minimum && match[0].length <= maximum) {
            return match[0];
          }
        }
      } else if (parts[1] === 'an') {
        if (match = /^[0-9A-Za-z.,\-()/= ]*$/.exec(input)) {
          if (match[0].length >= minimum && match[0].length <= maximum) {
            return match[0];
          }
        }
      } else if (parts[1] === 'n') {
        let length;
        if (match = /^([0-9]*)\.?([0-9]*)$/.exec(input)) {
          length = match[1].length + match[2].length;
          if (length >= minimum && length <= maximum) {
            return parseFloat(match[1] + '.' + match[2]);
          }
        }
      }
      throw new Error('Couldn\'t parse ' + input + ' with format string ' + format);
    } else {
      throw new Error('Couldn\'t understand the format string ' + format);
    }
  }
  /**
   * @summary Parse a UN/EDIFACT document.
   * @param {String} input The input document.
   * @param {Object} options Any options to use.
   *
   * This parser behaves similar to a SAX parser. It parses the document but by
   * itself does not construct any output. To get the parser to do anything
   * useful, user-defined hooks should be provided.
   */
  parse(input, options) {
    let match;

    this.reset();
    options = options || {};

    // Read any EDIFACT special characters from options if provided.
    let component_data_separator = options.component_data_separator || ':';
    let data_element_separator = options.data_element_separator || '+';
    let decimal_mark = options.decimal_mark || '.';
    let release_character = options.release_character || '?';
    let segment_terminator = options.segment_terminator || '\'';

    // Match the UNA header if present.
    let una = /UNA(.)(.)(.)(.)\ (.)[\n\r]*/g;
    if (match = una.exec(input)) {
      component_data_separator = match[1];
      data_element_separator = match[2];
      decimal_mark = match[3];
      release_character = match[4];
      segment_terminator = match[5];
      this._index = una.lastIndex;
    }

    // Construct a finite state machine describing the states of the parser.
    let machine = {};
    machine['segment'] = {};
    machine['element'] = {};
    machine['component'] = {};
    machine['segment'][data_element_separator] = 'element';
    machine['segment'][segment_terminator] = 'segment';
    machine['element'][data_element_separator] = 'element';
    machine['element'][component_data_separator] = 'component';
    machine['element'][segment_terminator] = 'segment';
    machine['component'][data_element_separator] = 'element';
    machine['component'][component_data_separator] = 'component';
    machine['component'][segment_terminator] = 'segment';

    // Construct a regex to match tokens from the EDIFACT message.
    let separator = (component_data_separator + '|' + data_element_separator + '|' + segment_terminator).replace(/([+?*(){}\[\]])/g, '\\$1');
    let escape = release_character.replace(/([+?*(){}\[\]])/g, '\\$1');
    // A regex with reluctant matchers.
    let expression = '((?:(?:' + escape + ')?.)*?)(' + separator + ')[\n\r]*';
    // A regex with greedy matchers.
    //let expression = '((?:' + escape + '.|(?!' + separator + ').)*)(' + separator + ')';
    // A regex based on negative lookaround.
    //let expression = '(.*?)((?<!' + escape + ')' + separator + ')';
    let regex = new RegExp(expression, 'g');
    regex.lastIndex = una.lastIndex;

    // Start reading tokens from the input.
    while (match = regex.exec(input)) {
      let value = match[1];
      let transition = match[2];
      let definition;
      if (this._index !== match.index) {
        throw Error('Unmatched sequence at index ' + this._index + ': ' + value);
      }
      switch (this._state.mode) {
        case 'segment':
        this._state.segment = value;
        this._state.elements = [];
        this._state.element = undefined;
        break;
        case 'element':
        definition = this._segments[this._state.segment];
        if (definition) {
          if (this._state.elements.length < definition.elements.length) {
            this._state.element = definition.elements[this._state.elements.length];
          } else {
            throw new Error('Maximum number of elements exceeded for segment ' + this._state.segment);
          }
        }
        this._state.components = this._state.elements[this._state.elements.push([]) - 1];
        case 'component':
        definition = this._elements[this._state.element];
        if (definition) {
          if (this._state.components.length < definition.components.length) {
            let format = definition.components[this._state.components.length];
            value = this.component(value, format);
          } else {
            throw new Error('Maximum number of components exceeded for element ' + this._state.element + ' in segment ' + this._state.segment);
          }
        }
        this._state.components.push(value);
        break;
      }
      this._state.mode = machine[this._state.mode][transition];
      if (this._state.mode === undefined) {
        throw Error('Invalid character ' + transition + ' after reading: ' + value);
      }
      switch (this._state.mode) {
        case 'segment':
        definition = this._segments[this._state.segment];
        if (definition && this._state.elements.length < this._segments[this._state.segment].requires) {
          throw new Error('Segment ' + this._state.segment + ' requires at least ' + this._segments[this._state.segment].requires + ' elements');
        }
        case 'element':
        definition = this._elements[this._state.element];
        if (definition && this._state.components.length < this._elements[this._state.element].requires) {
          throw new Error('Element ' + this._state.element + ' in segment ' + this._state.segment + ' requires at least ' + this._elements[this._state.element].requires + ' components');
        }
        break;
      }
      switch (this._state.mode) {
        case 'segment':
        for (let i = 0; i < this._hooks.length; i++) {
          this._hooks[i](this);
        }
        if (this._segmentHooks[this._state.segment]) {
          for (let i = 0; i < this._segmentHooks[this._state.segment].length; i++) {
            this._segmentHooks[this._state.segment][i](this);
          }
        }
        break;
      }
      this._index = regex.lastIndex;
    }
    if (this._state.mode !== 'segment') {
      throw new Error('Encountered an unterminated segment at the end of the input');
    }
    if (this._index !== input.length) {
      throw new Error('Invalid input encountered at index ' + regex.lastIndex);
    }
  }
};

/**
 * The Reader class extends Parser. It adds a default hook which constructs a
 * javascript array representing the original document.
 */
class Reader extends Parser {
  constructor() {
    super();
    this.hook(Reader.addSegment);
  }
  reset() {
    super.reset();
    this.state('result', []);
  }
  /**
   * @summary Parse the input document and return it as a javascript array.
   * @param {String} input The input document.
   * @param {Object} options Any options to use.
   */
  parse(document, options) {
    super.parse(document, options);
    return this.state('result');
  }
  static addSegment(parser) {
    let segment = {};
    segment.name = parser.state('segment');
    segment.elements = parser.state('elements');
    parser.state('result').push(segment);
  }
};

module.exports.Parser = Parser;
module.exports.Reader = Reader;
<|endoftext|>


<commit_before>import path from 'path';
var browserifyBuiltins = require('./builtins.json')
import lookup_path from './lookup';
import {package_fetch, version_fetch, tarball_fetch,
		package_cache, version_cache, tarball_cache} from './cache';

var npm_modules_cache = {};
var npm_resolve_cache = {};

export function reset(){
	npm_modules_cache = {};
	npm_resolve_cache = {};
}

export function extract_deps(code){
	// https://github.com/jrburke/requirejs/blob/master/require.js#L2033
	var commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,
	    cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g;
	var johnny_deps = [];
	code
		.replace(commentRegExp, '')
		.replace(cjsRequireRegExp, function (match, dep) {
		    johnny_deps.push(dep);
		});
	return johnny_deps;
}

function subresolve(pkg, filename, subdep){
	if(subdep.split("/")[0] == pkg.name){
		// loading another file from same package but by name
		return [subdep, pkg.version]
	}else if(subdep.startsWith('.')){
		// resolve a local dependency by turning it into an external one
		return [pkg.name + '/' + path.join(path.dirname(filename), subdep), pkg.version]
	}else{
		// resolve an external dependency
		var subname = subdep.split("/")[0];
		var subver = (pkg.dependencies && pkg.dependencies[subname]) ||
					 (pkg.devDependencies && pkg.devDependencies[subname]) ||
					 'latest';
		return [subdep, subver]
	}
}

async function resolve(dep, version = 'latest'){
	if(dep in browserifyBuiltins){
		console.debug('substituting', browserifyBuiltins[dep], 'for package', dep)
		dep = browserifyBuiltins[dep];
	}
	let name = dep.split("/")[0],
		main = dep.split("/").slice(1).join('/');
	let pkg = await version_fetch(name, version);
	let files = await tarball_fetch(pkg._id);
	var contents = lookup_path(pkg, files, main)
	return { pkg, main, contents }
}

export function resolveSync(dep, version = 'latest'){
	if(dep in browserifyBuiltins) dep = browserifyBuiltins[dep];
	let name = dep.split("/")[0],
		main = dep.split("/").slice(1).join('/');
	let pkg = version_cache(name, version);
	let files = tarball_cache(pkg._id);
	var contents = lookup_path(pkg, files, main)
	return { pkg, main, contents }
}

export async function recursiveResolve(dep, version = 'latest', opts = {}, level = 0){
	if((dep + '@' + version) in npm_resolve_cache) return; // it's already been resolved woo
	let { pkg, contents, main } = await resolve(dep, version);
	let id = pkg._id;
	if(!contents) throw new Error(`No file found at "${main}" for package ${id} (require ${dep}@${version}) `);
	npm_resolve_cache[dep + '@' + version] = 1;
	var subdeps = extract_deps(contents.data)
	if(opts.callback) opts.callback(id);
	console[level?'group':'groupCollapsed'](id + ':' + contents.filename + ` (depth ${level + 1})`)
	for(let subdep of subdeps){
		try {
			await recursiveResolve(...subresolve(pkg, contents.filename, subdep), opts, level + 1)
		} catch (err) {
			if(opts.error) opts.error(err, subdep);
		}

	}
	console.groupEnd(id + ':' + contents.filename + ` (depth ${level + 1})`)
}

export function requireModule(dep, version = 'latest'){
	var {contents, pkg} = resolveSync(dep, version)
	if(!contents) throw new Error(`File "${dep}" not found in package ${pkg._id}`);
	if(pkg._id in npm_modules_cache && contents.filename in npm_modules_cache[pkg._id]){
		return npm_modules_cache[pkg._id][contents.filename].exports
	}

	if(contents.filename.endsWith(".json")){
		npm_modules_cache[pkg._id][contents.filename] = {
			exports: JSON.parse(contents.data)
		}
		return npm_modules_cache[pkg._id][contents.filename].exports;
	}
	var preamble = 'var '+['exports', 'require', 'process', '__filename', '__dirname', 'Buffer', 'global']
		.map(x => `${x} = module.${x}`).join(', ') + ';';

	if(pkg.name == 'http-browserify') preamble += 'var window = global;';

	var fullpath = pkg._id + '/' + contents.filename;
	var config = { fullpath, filename: contents.filename, id: pkg._id };

	eval.call(global, `// Module wrapped for Carbide VM
	;(function(module){\n${preamble};
	\n\n${contents.data}\n\n
	})(__prepareModule(${JSON.stringify(config)}));
	\n//# sourceURL=mininpm:///${fullpath}?`);

	return npm_modules_cache[pkg._id][contents.filename].exports;
}

global.__prepareModule = function __prepareModule(config){
	var {filename, id, fullpath} = config;
	if(!(id in npm_modules_cache)) npm_modules_cache[id] = {};
	var [name, version] = id.split('@');
	var pkg = version_cache(name, version);
	process.platform = 'linux';
	npm_modules_cache[id][filename] = {
		exports: {},
		require: function(path){
			return requireModule(...subresolve(pkg, filename, path))
		},
		Buffer: Buffer,
		// console: {},
		process: process,
		global: global,
		__filename: fullpath,
		__dirname: path.dirname(fullpath)
	}
	return npm_modules_cache[id][filename];
}<commit_msg>hoisting use strict<commit_after>import path from 'path';
var browserifyBuiltins = require('./builtins.json')
import lookup_path from './lookup';
import {package_fetch, version_fetch, tarball_fetch,
		package_cache, version_cache, tarball_cache} from './cache';

var npm_modules_cache = {};
var npm_resolve_cache = {};

export function reset(){
	npm_modules_cache = {};
	npm_resolve_cache = {};
}

export function extract_deps(code){
	// https://github.com/jrburke/requirejs/blob/master/require.js#L2033
	var commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,
	    cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g;
	var johnny_deps = [];
	code
		.replace(commentRegExp, '')
		.replace(cjsRequireRegExp, function (match, dep) {
		    johnny_deps.push(dep);
		});
	return johnny_deps;
}

function subresolve(pkg, filename, subdep){
	if(subdep.split("/")[0] == pkg.name){
		// loading another file from same package but by name
		return [subdep, pkg.version]
	}else if(subdep.startsWith('.')){
		// resolve a local dependency by turning it into an external one
		return [pkg.name + '/' + path.join(path.dirname(filename), subdep), pkg.version]
	}else{
		// resolve an external dependency
		var subname = subdep.split("/")[0];
		var subver = (pkg.dependencies && pkg.dependencies[subname]) ||
					 (pkg.devDependencies && pkg.devDependencies[subname]) ||
					 'latest';
		return [subdep, subver]
	}
}

async function resolve(dep, version = 'latest'){
	if(dep in browserifyBuiltins){
		console.debug('substituting', browserifyBuiltins[dep], 'for package', dep)
		dep = browserifyBuiltins[dep];
	}
	let name = dep.split("/")[0],
		main = dep.split("/").slice(1).join('/');
	let pkg = await version_fetch(name, version);
	let files = await tarball_fetch(pkg._id);
	var contents = lookup_path(pkg, files, main)
	return { pkg, main, contents }
}

export function resolveSync(dep, version = 'latest'){
	if(dep in browserifyBuiltins) dep = browserifyBuiltins[dep];
	let name = dep.split("/")[0],
		main = dep.split("/").slice(1).join('/');
	let pkg = version_cache(name, version);
	let files = tarball_cache(pkg._id);
	var contents = lookup_path(pkg, files, main)
	return { pkg, main, contents }
}

export async function recursiveResolve(dep, version = 'latest', opts = {}, level = 0){
	if((dep + '@' + version) in npm_resolve_cache) return; // it's already been resolved woo
	let { pkg, contents, main } = await resolve(dep, version);
	let id = pkg._id;
	if(!contents) throw new Error(`No file found at "${main}" for package ${id} (require ${dep}@${version}) `);
	npm_resolve_cache[dep + '@' + version] = 1;
	var subdeps = extract_deps(contents.data)
	if(opts.callback) opts.callback(id);
	console[level?'group':'groupCollapsed'](id + ':' + contents.filename + ` (depth ${level + 1})`)
	for(let subdep of subdeps){
		try {
			await recursiveResolve(...subresolve(pkg, contents.filename, subdep), opts, level + 1)
		} catch (err) {
			if(opts.error) opts.error(err, subdep);
		}

	}
	console.groupEnd(id + ':' + contents.filename + ` (depth ${level + 1})`)
}

export function requireModule(dep, version = 'latest'){
	var {contents, pkg} = resolveSync(dep, version)
	if(!contents) throw new Error(`File "${dep}" not found in package ${pkg._id}`);
	if(pkg._id in npm_modules_cache && contents.filename in npm_modules_cache[pkg._id]){
		return npm_modules_cache[pkg._id][contents.filename].exports
	}

	if(contents.filename.endsWith(".json")){
		npm_modules_cache[pkg._id][contents.filename] = {
			exports: JSON.parse(contents.data)
		}
		return npm_modules_cache[pkg._id][contents.filename].exports;
	}

	var preamble = 'var '+['exports', 'require', 'process', '__filename', '__dirname', 'Buffer', 'global']
		.map(x => `${x} = module.${x}`).join(', ') + ';';

	if(pkg.name == 'http-browserify') preamble += 'var window = global;';

	var fullpath = pkg._id + '/' + contents.filename;
	var config = { fullpath, filename: contents.filename, id: pkg._id };
	global.eval(__prepareModule(contents.data, config));

	// global.eval(`// Module wrapped for Carbide VM
	// ;(function(module){\n${preamble};
	// \n\n${contents.data}\n\n
	// }).call(this, __prepareModule(${JSON.stringify(config)}));
	// \n//# sourceURL=mininpm:///${fullpath}?`);

	return npm_modules_cache[pkg._id][contents.filename].exports;
}

global.__prepareModule = function __prepareModule(code, config){
	var {filename, id, fullpath} = config;
	if(!(id in npm_modules_cache)) npm_modules_cache[id] = {};
	var [name, version] = id.split('@');
	var pkg = version_cache(name, version);
	process.platform = 'linux';

	npm_modules_cache[id][filename] = {
		exports: {},
		require: function(path){
			return requireModule(...subresolve(pkg, filename, path))
		},
		Buffer: Buffer,
		// console: {},
		process: process,
		global: global,
		__filename: fullpath,
		__dirname: path.dirname(fullpath)
	}
	var varys = npm_modules_cache[id][filename];
	varys.module = varys;
	if(typeof code == 'string'){
        return `// Module wrapped for Carbide MiniNPM
        (function ModuleClosure(${Object.keys(varys).join(', ')}){
            \n${code}\n
        \n}).apply(null, __prepareModule(0, ${JSON.stringify(config)}));
		\n//# sourceURL=mininpm:///${fullpath}?`
    }
	return Object.keys(varys).map(x => varys[x]);
}

// global.__prepareModule = function __prepareModule(config){
// 	var {filename, id, fullpath} = config;
// 	if(!(id in npm_modules_cache)) npm_modules_cache[id] = {};
// 	var [name, version] = id.split('@');
// 	var pkg = version_cache(name, version);
// 	process.platform = 'linux';
// 	npm_modules_cache[id][filename] = {
// 		exports: {},
// 		require: function(path){
// 			return requireModule(...subresolve(pkg, filename, path))
// 		},
// 		Buffer: Buffer,
// 		// console: {},
// 		process: process,
// 		global: global,
// 		__filename: fullpath,
// 		__dirname: path.dirname(fullpath)
// 	}
// 	return npm_modules_cache[id][filename];
// }<|endoftext|>