<commit_before>'use strict';

const EventEmitter = require('events').EventEmitter;
const Extension = require('./Extension');
const Command = require('./Command');

const stack = require('./stack');
const path = require('path');

module.exports = class StumbleCore extends EventEmitter {
  constructor (opts) {
    super();

    opts = opts || {};

    this.commands = new Map();
    this.extensions = new Map();

    this.__cache = {};
    this.__cacheduration = opts.cacheduration || 1000;
    this.__cachetimer = null;
  }

  use (extension, pending) {
    if (Array.isArray(extension))
      extension.forEach(ext => this.use(ext, extension));
    else {
      if (!extension || !Object.keys(extension).length)
        throw new Error('EXT: Attempted to use empty extension.');

      if (this.__cache[extension.handle]) return this;

      if (extension.needs) extension.needs.forEach(ext => {
        if (!this.extensions.has(ext)) {
          let missing = true;
          let len = null;

          if (pending && (len = pending.length)) {
            for (let i = 0; i < len; i++) {
              if (pending[i].handle === ext) {
                clearTimeout(this.__cachetimer);

                this.use(pending[i], pending);
                this.__cache[ext] = true;

                this.__cachetimer = setTimeout(() => {
                  this.__cache = {};
                  this.__cachetimer = null;
                }, this.__cacheduration);

                missing = false;
                break;
              }
            }
          }

          if (missing) throw new Error(`EXT: Missing required [ ${ext} ].`);
        }
      });

      const extensions = extension.extensions;
      const commands = extension.commands;
      const init = extension.init;

      extension = new Extension(extension);

      const handle = extension.handle;

      if (this.extensions.has(handle))
        throw new Error(`EXT: Attempted to use duplicate [ ${handle} ].`);

      if (init) init.call(this, this);

      const exec = extension.exec;

      if (!handle && exec)
        throw new Error('EXT: Attempted to add exec without handle.');

      if (!handle && extensions)
        throw new Error('EXT: Attempted to add extensions without handle.');

      if (extensions) {
        this.use(extensions);
        extension.extensions = extensions.map(ext => ext.handle);
      }

      if (!handle && commands)
        throw new Error('EXT: Attempted to add commands without handle.');

      if (commands) {
        this.define(commands);
        extension.commands = commands.map(cmd => cmd.handle);
      }

      if (handle) this.extensions.set(handle, extension);
    }
    return this;
  }

  execute (handle, data, roll) {
    const extension = this.extensions.get(handle);

    if (!extension || !extension.exec) return null;

    if (extension.hooks) {
      roll = extension.hooks.map(hook => this.execute(hook, data, roll));
    }

    return extension.exec.call(this, data, roll || null);
  }

  define (command) {
    if (Array.isArray(command)) command.forEach(cmd => this.define(cmd));
    else {
      if (!command || !(command.handle && command.exec))
        throw new Error('CMD: Attempted to define empty command.');

      const handle = command.handle;

      if (this.commands.has(handle))
        throw new Error(`CMD: Attempted to define duplicate [ ${handle} ].`);

      if (/\s/.test(handle))
        throw new Error(`CMD: Whitespace in command [ ${handle} ].`);

      this.commands.set(handle, new Command(command));<commit_msg>improved lookahead loading<commit_after>'use strict';

const EventEmitter = require('events').EventEmitter;
const Extension = require('./Extension');
const Command = require('./Command');

const stack = require('./stack');
const path = require('path');

module.exports = class StumbleCore extends EventEmitter {
  constructor (opts) {
    super();

    opts = opts || {};

    this.commands = new Map();
    this.extensions = new Map();

    this.__cache = {};
    this.__cacheduration = opts.cacheduration || 1000;
    this.__cachetimer = null;
  }

  use (extension, pending, index) {
    if (Array.isArray(extension))
      extension.forEach((ext, i) => this.use(ext, extension, i));
    else {
      if (!extension || !Object.keys(extension).length)
        throw new Error('EXT: Attempted to use empty extension.');

      if (this.__cache[extension.handle]) return this;

      if (extension.needs) extension.needs.forEach(ext => {
        if (!this.extensions.has(ext)) {
          let missing = true;
          let length = null;

          if (pending && (length = pending.length))
            for (let offset = index + 1 || 1; offset < length; offset++)
              if (pending[offset].handle === ext) {
                clearTimeout(this.__cachetimer);

                this.use(pending[offset], pending, offset);
                this.__cache[ext] = true;

                this.__cachetimer = setTimeout(() => {
                  this.__cache = {};
                  this.__cachetimer = null;
                }, this.__cacheduration);

                missing = false;
                break;
              }

          if (missing) throw new Error(`EXT: Missing required [ ${ext} ].`);
        }
      });

      const extensions = extension.extensions;
      const commands = extension.commands;
      const init = extension.init;

      extension = new Extension(extension);

      const handle = extension.handle;

      if (this.extensions.has(handle))
        throw new Error(`EXT: Attempted to use duplicate [ ${handle} ].`);

      if (init) init.call(this, this);

      const exec = extension.exec;

      if (!handle && exec)
        throw new Error('EXT: Attempted to add exec without handle.');

      if (!handle && extensions)
        throw new Error('EXT: Attempted to add extensions without handle.');

      if (extensions) {
        this.use(extensions);
        extension.extensions = extensions.map(ext => ext.handle);
      }

      if (!handle && commands)
        throw new Error('EXT: Attempted to add commands without handle.');

      if (commands) {
        this.define(commands);
        extension.commands = commands.map(cmd => cmd.handle);
      }

      if (handle) this.extensions.set(handle, extension);
    }

    return this;
  }

  execute (handle, data, roll) {
    const extension = this.extensions.get(handle);

    if (!extension || !extension.exec) return null;

    if (extension.hooks) {
      roll = extension.hooks.map(hook => this.execute(hook, data, roll));
    }

    return extension.exec.call(this, data, roll || null);
  }

  define (command) {
    if (Array.isArray(command)) command.forEach(cmd => this.define(cmd));
    else {
      if (!command || !(command.handle && command.exec))
        throw new Error('CMD: Attempted to define empty command.');

      const handle = command.handle;

      if (this.commands.has(handle))
        throw new Error(`CMD: Attempted to define duplicate [ ${handle} ].`);

      if (/\s/.test(handle))
        throw new Error(`CMD: Whitespace in command [ ${handle} ].`);

      this.commands.set(handle, new Command(command));
{'fraction_non_alphanumeric': 0.13791666666666666, 'fraction_numerical': 0.0015277777777777779, 'mean_word_length': 2.339981447124304, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4074803', 'n_tokens_mistral': 1994, 'n_tokens_neox': 1913, 'n_words': 535}