<commit_before>import electron from 'electron';
import {forEach, values} from 'lodash';
import {Observable, Disposable, CompositeDisposable, SerialDisposable} from 'rx-lite';
import {getSubstitutionRegExp, getSmartQuotesRegExp, getSmartDashesRegExp,
  scrubInputString, formatReplacement} from './regular-expressions';
import {isUndoRedoEvent} from './undo-redo-event';

const packageName = 'electron-text-substitutions';
const d = require('debug-electron')(packageName);

const registerForPreferenceChangedIpcMessage = `${packageName}-register-renderer`;
const unregisterForPreferenceChangedIpcMessage = `${packageName}-unregister-renderer`;
const preferenceChangedIpcMessage = `${packageName}-preference-changed`;

const userDefaultsTextSubstitutionsKey = 'NSUserDictionaryReplacementItems';
const userDefaultsSmartQuotesKey = 'NSAutomaticQuoteSubstitutionEnabled';
const userDefaultsSmartDashesKey = 'NSAutomaticDashSubstitutionEnabled';

const textPreferenceChangedKeys = [
  'IMKTextReplacementDidChangeNotification',
  'NSAutomaticQuoteSubstitutionEnabledChanged',
  'NSAutomaticDashSubstitutionEnabledChanged'
];

let ipcMain, ipcRenderer, systemPreferences;
let registeredWebContents = {};

/**
 * Adds an `input` event listener to the given element (an <input> or
 * <textarea>) that will substitute text based on the user's replacements in
 * `NSUserDefaults`.
 *
 * In addition, this method will listen for changes to `NSUserDefaults` and
 * update accordingly.
 *
 * @param  {EventTarget} element          The DOM node to listen to; should fire the `input` event
 * @param  {Object} preferenceOverrides   Used to override text preferences in testing
 *
 * @return {Disposable}                   A `Disposable` that will clean up everything this method did
 */
export default function performTextSubstitution(element, preferenceOverrides = null) {
  if (!element || !element.addEventListener) throw new Error(`Element is null or not an EventTarget`);
  if (!process || !process.type === 'renderer') throw new Error(`Not in an Electron renderer context`);
  if (process.platform !== 'darwin') throw new Error(`Only supported on OS X`);

  ipcRenderer = ipcRenderer || electron.ipcRenderer;
  systemPreferences = systemPreferences || electron.remote.systemPreferences;

  if (!systemPreferences || !systemPreferences.getUserDefault) {
    throw new Error(`Electron ${process.versions.electron} is not supported`);
  }

  ipcRenderer.send(registerForPreferenceChangedIpcMessage);

  window.addEventListener('beforeunload', () => {
    d(`Window unloading, unregister any listeners`);
    ipcRenderer.send(unregisterForPreferenceChangedIpcMessage);
  });

  let currentAttach = assignDisposableToListener(element, preferenceOverrides);

  ipcRenderer.on(preferenceChangedIpcMessage, () => {
    d(`User modified text preferences, reattaching listener`);
    assignDisposableToListener(element, preferenceOverrides, currentAttach);
  });

  return currentAttach;
}

/**
 * Subscribes to text preference changed notifications and notifies listeners
 * in renderer processes. This method must be called from the main process, and
 * should be called before any renderer process calls `performTextSubstitution`.
 *
 * @return {Disposable}  A `Disposable` that will clean up everything this method did
 */
export function listenForPreferenceChanges() {
  if (!process || !process.type === 'browser') throw new Error(`Not in an Electron browser context`);
  if (process.platform !== 'darwin') throw new Error(`Only supported on OS X`);

  ipcMain = ipcMain || electron.ipcMain;
  systemPreferences = systemPreferences || electron.systemPreferences;

  ipcMain.on(registerForPreferenceChangedIpcMessage, ({sender}) => {
    let id = sender.getId();
    d(`Registering webContents ${id} for preference changes`);
    registeredWebContents[id] = { id, sender };
  });

  ipcMain.on(unregisterForPreferenceChangedIpcMessage, ({sender}) => {
    d(`Unregistering webContents ${sender.getId()}`);
    delete registeredWebContents[sender.getId()];
  });

  let notificationDisp = Observable.fromArray(textPreferenceChangedKeys)
    .flatMap((key) => observableForPreferenceNotification(key))
    .debounce(1000)
    .subscribe(notifyAllListeners);

  return new CompositeDisposable(
    notificationDisp,
    new Disposable(() => ipcMain.removeAllListeners(registerForPreferenceChangedIpcMessage)),
    new Disposable(() => ipcMain.removeAllListeners(unregisterForPreferenceChangedIpcMessage))
  );
}

/**
 * Creates an Observable that will `onNext` when the given key in
 * `NSUserDefaults` changes.
 *
 * @param  {String} preferenceChangedKey  The key to listen for
 * @return {Disposable}                   A Disposable that will unsubscribe the listener
 */
function observableForPreferenceNotification(preferenceChangedKey) {
  return Observable.create((subj) => {
    let subscriberId = systemPreferences.subscribeNotification(preferenceChangedKey, () => {
      d(`Got a ${preferenceChangedKey}`);
      subj.onNext(preferenceChangedKey);
    });

    return new Disposable(() => systemPreferences.unsubscribeNotification(subscriberId));
  });
}

/**
 * Sends an IPC message to each `WebContents` that is doing text substitution,
 * unless it has been destroyed, in which case remove it from our list.
 */
function notifyAllListeners() {
  forEach(values(registeredWebContents), ({id, sender}) => {
    if (sender.isDestroyed() || sender.isCrashed()) {
      d(`WebContents ${id} is gone, removing it`);
      delete registeredWebContents[id];
    } else {
      sender.send(preferenceChangedIpcMessage);
    }
  });
}

function assignDisposableToListener(element, preferenceOverrides, currentAttach = null) {
  let textPreferences = preferenceOverrides || readSystemTextPreferences();
  let replacementItems = getReplacementItems(textPreferences);

  currentAttach = currentAttach || new SerialDisposable();
  currentAttach.setDisposable(addInputListener(element, replacementItems));
  return currentAttach;
}

function readSystemTextPreferences() {
  return {
    substitutions: systemPreferences.getUserDefault(userDefaultsTextSubstitutionsKey, 'array') || [],
    useSmartQuotes: systemPreferences.getUserDefault(userDefaultsSmartQuotesKey, 'boolean'),
    useSmartDashes: systemPreferences.getUserDefault(userDefaultsSmartDashesKey, 'boolean')
  };
}

/**
 * @typedef {Object} TextSubstitution
 * @property {String} replace The text to replace
 * @property {String} with    The replacement text
 * @property {Bool}   on      True if this substitution is enabled
 */

 /**
  * Creates a regular expression for each text substitution entry, in addition
  * to expressions for smart quotes and dashes (if they're enabled).
  *
  * @param  {Array<TextSubstitution>} {substitutions  An array of text substitution entries
  * @param  {Bool} useSmartQuotes                     True if smart quotes is on
  * @param  {Bool} useSmartDashes}                    True if smart dashes is on
  * @return {Array<ReplacementItem>}                  An array of replacement items
  */
function getReplacementItems({substitutions, useSmartQuotes, useSmartDashes}) {
  d(`Smart quotes are ${useSmartQuotes ? 'on' : 'off'}`);
  d(`Smart dashes are ${useSmartDashes ? 'on' : 'off'}`);

  let additionalReplacements = [
    ...(useSmartQuotes ? getSmartQuotesRegExp() : []),
    ...(useSmartDashes ? getSmartDashesRegExp() : [])
  ];

  d(`Found ${substitutions.length} substitutions in NSUserDictionaryReplacementItems`);

  // NB: Run each replacement string through our smart quotes & dashes regex,
  // so that an input event doesn't cause chained substitutions.
  let userDictionaryReplacements = substitutions
    .filter((substitution) => substitution.on !== false)
    .map((substitution) => getSubstitutionRegExp(substitution.replace,
      scrubInputString(substitution.with, additionalReplacements)));

  return [
    ...userDictionaryReplacements,
    ...additionalReplacements
  ];
}

/**
 * Subscribes to the `input` event and performs text substitution.
 *
 * @param  {EventTarget} element                      The DOM node to listen to
 * @param  {Array<ReplacementItem>} replacementItems  An array of replacement items
 * @return {Disposable}                               A `Disposable` that will remove the listener
 */
function addInputListener(element, replacementItems) {
  let ignoreEvent = false;

  let inputListener = () => {
    if (ignoreEvent) return;
    ignoreEvent = true;

    for (let {regExp, replacement} of replacementItems) {
      let match = element.value.match(regExp);
      if (match && match.length === 3) {
        d(`Got a match of length ${match[0].length} at index ${match.index}: ${JSON.stringify(match)}`);

        let selection = {
          startIndex: match.index,
          endIndex: match.index + match[0].length
        };

        // Are we inserting characters before the end of input (vs appending?)
        let isInserting = element.selectionEnd !== element.value.length;

        replaceText(element, selection, formatReplacement(match, replacement));

        // Our regex includes trailing whitespace / word boundaries, which,
        // when inserting text, cause the caret position to jump to the end of
        // the match. We subtract everything to the right of the match
        // to preserve the caret position.
        if (isInserting) element.selectionEnd -= match[2].length;
      }
    }

    ignoreEvent = false;
  };

  let keyDownListener = (e) => {
    if (isUndoRedoEvent(e)) {
      d(`Undo or redo text from ${e.target.value}`);
      ignoreEvent = true;
    }
  };

  let keyUpListener = () => {
    ignoreEvent = false;
  };

  element.addEventListener('keydown', keyDownListener, true);
  element.addEventListener('keyup', keyUpListener, true);
  element.addEventListener('input', inputListener);

  d(`Added input listener to ${element.id} matching against ${replacementItems.length} replacements`);

  return new Disposable(() => {
    element.removeEventListener('keydown', keyDownListener);
    element.removeEventListener('keyup', keyUpListener);
    element.removeEventListener('input', inputListener);

    d(`Removed input listener from ${element.id}`);
  });
}

/**
 * Performs the actual text replacement using `dispatchEvent`. We use events to
 * preserve the user's cursor index and make the substitution undoable.
 *
 * @param  {EventTarget} element  The DOM node where text is being substituted
 * @param  {Number} {startIndex   Start index of the text to replace
 * @param  {Number} endIndex}     End index of the text to replace
 * @param  {String} newText       The text being inserted
 */
function replaceText(element, {startIndex, endIndex}, newText) {
  let textEvent = document.createEvent('TextEvent');
  textEvent.initTextEvent('textInput', true, true, null, newText);

  element.selectionStart = startIndex;
  element.selectionEnd = endIndex;

  d(`Replacing ${element.value.substring(startIndex, endIndex)} with ${newText}`);
  element.dispatchEvent(textEvent);
}
<commit_msg>Don't perform substitutions immediately after a paste.<commit_after>import electron from 'electron';
import {forEach, values} from 'lodash';
import {Observable, Disposable, CompositeDisposable, SerialDisposable} from 'rx-lite';
import {getSubstitutionRegExp, getSmartQuotesRegExp, getSmartDashesRegExp,
  scrubInputString, formatReplacement} from './regular-expressions';
import {isUndoRedoEvent} from './undo-redo-event';

const packageName = 'electron-text-substitutions';
const d = require('debug-electron')(packageName);

const registerForPreferenceChangedIpcMessage = `${packageName}-register-renderer`;
const unregisterForPreferenceChangedIpcMessage = `${packageName}-unregister-renderer`;
const preferenceChangedIpcMessage = `${packageName}-preference-changed`;

const userDefaultsTextSubstitutionsKey = 'NSUserDictionaryReplacementItems';
const userDefaultsSmartQuotesKey = 'NSAutomaticQuoteSubstitutionEnabled';
const userDefaultsSmartDashesKey = 'NSAutomaticDashSubstitutionEnabled';

const textPreferenceChangedKeys = [
  'IMKTextReplacementDidChangeNotification',
  'NSAutomaticQuoteSubstitutionEnabledChanged',
  'NSAutomaticDashSubstitutionEnabledChanged'
];

let ipcMain, ipcRenderer, systemPreferences;
let registeredWebContents = {};

/**
 * Adds an `input` event listener to the given element (an <input> or
 * <textarea>) that will substitute text based on the user's replacements in
 * `NSUserDefaults`.
 *
 * In addition, this method will listen for changes to `NSUserDefaults` and
 * update accordingly.
 *
 * @param  {EventTarget} element          The DOM node to listen to; should fire the `input` event
 * @param  {Object} preferenceOverrides   Used to override text preferences in testing
 *
 * @return {Disposable}                   A `Disposable` that will clean up everything this method did
 */
export default function performTextSubstitution(element, preferenceOverrides = null) {
  if (!element || !element.addEventListener) throw new Error(`Element is null or not an EventTarget`);
  if (!process || !process.type === 'renderer') throw new Error(`Not in an Electron renderer context`);
  if (process.platform !== 'darwin') throw new Error(`Only supported on OS X`);

  ipcRenderer = ipcRenderer || electron.ipcRenderer;
  systemPreferences = systemPreferences || electron.remote.systemPreferences;

  if (!systemPreferences || !systemPreferences.getUserDefault) {
    throw new Error(`Electron ${process.versions.electron} is not supported`);
  }

  ipcRenderer.send(registerForPreferenceChangedIpcMessage);

  window.addEventListener('beforeunload', () => {
    d(`Window unloading, unregister any listeners`);
    ipcRenderer.send(unregisterForPreferenceChangedIpcMessage);
  });

  let currentAttach = assignDisposableToListener(element, preferenceOverrides);

  ipcRenderer.on(preferenceChangedIpcMessage, () => {
    d(`User modified text preferences, reattaching listener`);
    assignDisposableToListener(element, preferenceOverrides, currentAttach);
  });

  return currentAttach;
}

/**
 * Subscribes to text preference changed notifications and notifies listeners
 * in renderer processes. This method must be called from the main process, and
 * should be called before any renderer process calls `performTextSubstitution`.
 *
 * @return {Disposable}  A `Disposable` that will clean up everything this method did
 */
export function listenForPreferenceChanges() {
  if (!process || !process.type === 'browser') throw new Error(`Not in an Electron browser context`);
  if (process.platform !== 'darwin') throw new Error(`Only supported on OS X`);

  ipcMain = ipcMain || electron.ipcMain;
  systemPreferences = systemPreferences || electron.systemPreferences;

  ipcMain.on(registerForPreferenceChangedIpcMessage, ({sender}) => {
    let id = sender.getId();
    d(`Registering webContents ${id} for preference changes`);
    registeredWebContents[id] = { id, sender };
  });

  ipcMain.on(unregisterForPreferenceChangedIpcMessage, ({sender}) => {
    d(`Unregistering webContents ${sender.getId()}`);
    delete registeredWebContents[sender.getId()];
  });

  let notificationDisp = Observable.fromArray(textPreferenceChangedKeys)
    .flatMap((key) => observableForPreferenceNotification(key))
    .debounce(1000)
    .subscribe(notifyAllListeners);

  return new CompositeDisposable(
    notificationDisp,
    new Disposable(() => ipcMain.removeAllListeners(registerForPreferenceChangedIpcMessage)),
    new Disposable(() => ipcMain.removeAllListeners(unregisterForPreferenceChangedIpcMessage))
  );
}

/**
 * Creates an Observable that will `onNext` when the given key in
 * `NSUserDefaults` changes.
 *
 * @param  {String} preferenceChangedKey  The key to listen for
 * @return {Disposable}                   A Disposable that will unsubscribe the listener
 */
function observableForPreferenceNotification(preferenceChangedKey) {
  return Observable.create((subj) => {
    let subscriberId = systemPreferences.subscribeNotification(preferenceChangedKey, () => {
      d(`Got a ${preferenceChangedKey}`);
      subj.onNext(preferenceChangedKey);
    });

    return new Disposable(() => systemPreferences.unsubscribeNotification(subscriberId));
  });
}

/**
 * Sends an IPC message to each `WebContents` that is doing text substitution,
 * unless it has been destroyed, in which case remove it from our list.
 */
function notifyAllListeners() {
  forEach(values(registeredWebContents), ({id, sender}) => {
    if (sender.isDestroyed() || sender.isCrashed()) {
      d(`WebContents ${id} is gone, removing it`);
      delete registeredWebContents[id];
    } else {
      sender.send(preferenceChangedIpcMessage);
    }
  });
}

function assignDisposableToListener(element, preferenceOverrides, currentAttach = null) {
  let textPreferences = preferenceOverrides || readSystemTextPreferences();
  let replacementItems = getReplacementItems(textPreferences);

  currentAttach = currentAttach || new SerialDisposable();
  currentAttach.setDisposable(addInputListener(element, replacementItems));
  return currentAttach;
}

function readSystemTextPreferences() {
  return {
    substitutions: systemPreferences.getUserDefault(userDefaultsTextSubstitutionsKey, 'array') || [],
    useSmartQuotes: systemPreferences.getUserDefault(userDefaultsSmartQuotesKey, 'boolean'),
    useSmartDashes: systemPreferences.getUserDefault(userDefaultsSmartDashesKey, 'boolean')
  };
}

/**
 * @typedef {Object} TextSubstitution
 * @property {String} replace The text to replace
 * @property {String} with    The replacement text
 * @property {Bool}   on      True if this substitution is enabled
 */

 /**
  * Creates a regular expression for each text substitution entry, in addition
  * to expressions for smart quotes and dashes (if they're enabled).
  *
  * @param  {Array<TextSubstitution>} {substitutions  An array of text substitution entries
  * @param  {Bool} useSmartQuotes                     True if smart quotes is on
  * @param  {Bool} useSmartDashes}                    True if smart dashes is on
  * @return {Array<ReplacementItem>}                  An array of replacement items
  */
function getReplacementItems({substitutions, useSmartQuotes, useSmartDashes}) {
  d(`Smart quotes are ${useSmartQuotes ? 'on' : 'off'}`);
  d(`Smart dashes are ${useSmartDashes ? 'on' : 'off'}`);

  let additionalReplacements = [
    ...(useSmartQuotes ? getSmartQuotesRegExp() : []),
    ...(useSmartDashes ? getSmartDashesRegExp() : [])
  ];

  d(`Found ${substitutions.length} substitutions in NSUserDictionaryReplacementItems`);

  // NB: Run each replacement string through our smart quotes & dashes regex,
  // so that an input event doesn't cause chained substitutions.
  let userDictionaryReplacements = substitutions
    .filter((substitution) => substitution.on !== false)
    .map((substitution) => getSubstitutionRegExp(substitution.replace,
      scrubInputString(substitution.with, additionalReplacements)));

  return [
    ...userDictionaryReplacements,
    ...additionalReplacements
  ];
}

/**
 * Subscribes to the `input` event and performs text substitution.
 *
 * @param  {EventTarget} element                      The DOM node to listen to
 * @param  {Array<ReplacementItem>} replacementItems  An array of replacement items
 * @return {Disposable}                               A `Disposable` that will remove the listener
 */
function addInputListener(element, replacementItems) {
  let ignoreEvent = false;

  let inputListener = () => {
    if (ignoreEvent) return;
    ignoreEvent = true;

    for (let {regExp, replacement} of replacementItems) {
      let match = element.value.match(regExp);
      if (match && match.length === 3) {
        d(`Got a match of length ${match[0].length} at index ${match.index}: ${JSON.stringify(match)}`);

        let selection = {
          startIndex: match.index,
          endIndex: match.index + match[0].length
        };

        // Are we inserting characters before the end of input (vs appending?)
        let isInserting = element.selectionEnd !== element.value.length;

        replaceText(element, selection, formatReplacement(match, replacement));

        // Our regex includes trailing whitespace / word boundaries, which,
        // when inserting text, cause the caret position to jump to the end of
        // the match. We subtract everything to the right of the match
        // to preserve the caret position.
        if (isInserting) element.selectionEnd -= match[2].length;
      }
    }

    ignoreEvent = false;
  };

  let keyDownListener = (e) => {
    if (isUndoRedoEvent(e)) {
      d(`Undo or redo text from ${e.target.value}`);
      ignoreEvent = true;
    }
  };

  let pasteListener = () => {
    ignoreEvent = true;
  };

  let keyUpListener = () => {
    ignoreEvent = false;
  };

  element.addEventListener('keydown', keyDownListener, true);
  element.addEventListener('paste', pasteListener, true);
  element.addEventListener('keyup', keyUpListener, true);
  element.addEventListener('input', inputListener);

  d(`Added input listener to ${element.id} matching against ${replacementItems.length} replacements`);

  return new Disposable(() => {
    element.removeEventListener('keydown', keyDownListener);
    element.removeEventListener('paste', pasteListener);
    element.removeEventListener('keyup', keyUpListener);
    element.removeEventListener('input', inputListener);

    d(`Removed input listener from ${element.id}`);
  });
}

/**
 * Performs the actual text replacement using `dispatchEvent`. We use events to
 * preserve the user's cursor index and make the substitution undoable.
 *
 * @param  {EventTarget} element  The DOM node where text is being substituted
 * @param  {Number} {startIndex   Start index of the text to replace
 * @param  {Number} endIndex}     End index of the text to replace
 * @param  {String} newText       The text being inserted
 */
function replaceText(element, {startIndex, endIndex}, newText) {
  let textEvent = document.createEvent('TextEvent');
  textEvent.initTextEvent('textInput', true, true, null, newText);

  element.selectionStart = startIndex;
  element.selectionEnd = endIndex;

  d(`Replacing ${element.value.substring(startIndex, endIndex)} with ${newText}`);
  element.dispatchEvent(textEvent);
}
<|endoftext|>


<commit_before>import {Origin} from 'aurelia-metadata';
import {Loader} from 'aurelia-loader';
import {DOM, PLATFORM} from 'aurelia-pal';

/**
* An implementation of the TemplateLoader interface implemented with text-based loading.
*/
export class TextTemplateLoader {
  /**
  * Loads a template.
  * @param loader The loader that is requesting the template load.
  * @param entry The TemplateRegistryEntry to load and populate with a template.
  * @return A promise which resolves when the TemplateRegistryEntry is loaded with a template.
  */
  loadTemplate(loader, entry) {
    return loader.loadText(entry.address).then(text => {
      entry.template = DOM.createTemplateFromMarkup(text);
    });
  }
}

function ensureOriginOnExports(executed, name) {
  let target = executed;
  let key;
  let exportedValue;

  if (target.__useDefault) {
    target = target.default;
  }

  Origin.set(target, new Origin(name, 'default'));

  for (key in target) {
    exportedValue = target[key];

    if (typeof exportedValue === 'function') {
      Origin.set(exportedValue, new Origin(name, key));
    }
  }

  return executed;
}

/**
* A default implementation of the Loader abstraction which works with webpack (extended common-js style).
*/
export class WebpackLoader extends Loader {
  constructor() {
    super();

    this.moduleRegistry = Object.create(null);
    this.loaderPlugins = Object.create(null);
    this.useTemplateLoader(new TextTemplateLoader());

    let that = this;

    this.addPlugin('template-registry-entry', {
      'fetch': function(address) {
        let entry = that.getOrCreateTemplateRegistryEntry(address);
        return entry.templateIsLoaded ? entry : that.templateLoader.loadTemplate(that, entry).then(x => entry);
      }
    });

    PLATFORM.eachModule = callback => {
      let registry = this.moduleRegistry;

      for (let key in registry) {
        try {
          if (callback(key, registry[key])) return;
        } catch (e) {}
      }
    };
  }

  _import(moduleId) {
    const moduleIdParts = moduleId.split('!');
    const path = moduleIdParts.splice(moduleIdParts.length - 1, 1)[0];
    const loaderPlugin = moduleIdParts.length === 1 ? moduleIdParts[0] : null;

    return new Promise((resolve, reject) => {
      try {
        if (loaderPlugin) {
          resolve(this.loaderPlugins[loaderPlugin].fetch(path));
        } else {
          require.ensure([], function(require) {
            const result = require('aurelia-loader-context/' + path);
            if (typeof(result) === 'function') { // because of async loading when the bundle loader is active
              result(res => resolve(res));
            } else {
              resolve(result);
            }
          }, 'app');
        }
      } catch (e) {
        reject(e);
      }
    });
  }

  /**
  * Maps a module id to a source.
  * @param id The module id.
  * @param source The source to map the module to.
  */
  map(id, source) {

  }

  /**
  * Normalizes a module id.
  * @param moduleId The module id to normalize.
  * @param relativeTo What the module id should be normalized relative to.
  * @return The normalized module id.
  */
  normalizeSync(moduleId, relativeTo) {
    return moduleId;
  }

  /**
  * Normalizes a module id.
  * @param moduleId The module id to normalize.
  * @param relativeTo What the module id should be normalized relative to.
  * @return The normalized module id.
  */
  normalize(moduleId, relativeTo) {
    return Promise.resolve(moduleId);
  }

  /**
  * Instructs the loader to use a specific TemplateLoader instance for loading templates
  * @param templateLoader The instance of TemplateLoader to use for loading templates.
  */
  useTemplateLoader(templateLoader) {
    this.templateLoader = templateLoader;
  }

  /**
  * Loads a collection of modules.
  * @param ids The set of module ids to load.
  * @return A Promise for an array of loaded modules.
  */
  loadAllModules(ids) {
    let loads = [];

    for (let i = 0, ii = ids.length; i < ii; ++i) {
      loads.push(this.loadModule(ids[i]));
    }

    return Promise.all(loads);
  }

  /**
  * Loads a module.
  * @param id The module id to normalize.
  * @return A Promise for the loaded module.
  */
  loadModule(id) {
    let existing = this.moduleRegistry[id];
    if (existing) {
      return Promise.resolve(existing);
    }

    return new Promise((resolve, reject) => {
      try {
        this._import(id).then(m => {
          this.moduleRegistry[id] = m;
          resolve(ensureOriginOnExports(m, id));
        });
      } catch (e) {
        reject(e);
      }
    });
  }

  /**
  * Loads a template.
  * @param url The url of the template to load.
  * @return A Promise for a TemplateRegistryEntry containing the template.
  */
  loadTemplate(url) {
    return this._import(this.applyPluginToUrl(url, 'template-registry-entry'));
  }

  /**
  * Loads a text-based resource.
  * @param url The url of the text file to load.
  * @return A Promise for text content.
  */
  loadText(url) {
    return this._import(url);
  }

  /**
  * Alters a module id so that it includes a plugin loader.
  * @param url The url of the module to load.
  * @param pluginName The plugin to apply to the module id.
  * @return The plugin-based module id.
  */
  applyPluginToUrl(url, pluginName) {
    return `${pluginName}!${url}`;
  }

  /**
  * Registers a plugin with the loader.
  * @param pluginName The name of the plugin.
  * @param implementation The plugin implementation.
  */
  addPlugin(pluginName, implementation) {
    this.loaderPlugins[pluginName] = implementation;
  }
}

PLATFORM.Loader = WebpackLoader;
<commit_msg>feat(index): support css-loader<commit_after>import {Origin} from 'aurelia-metadata';
import {Loader} from 'aurelia-loader';
import {DOM, PLATFORM} from 'aurelia-pal';

/**
* An implementation of the TemplateLoader interface implemented with text-based loading.
*/
export class TextTemplateLoader {
  /**
  * Loads a template.
  * @param loader The loader that is requesting the template load.
  * @param entry The TemplateRegistryEntry to load and populate with a template.
  * @return A promise which resolves when the TemplateRegistryEntry is loaded with a template.
  */
  loadTemplate(loader, entry) {
    return loader.loadText(entry.address).then(text => {
      entry.template = DOM.createTemplateFromMarkup(text);
    });
  }
}

function ensureOriginOnExports(executed, name) {
  let target = executed;
  let key;
  let exportedValue;

  if (target.__useDefault) {
    target = target.default;
  }

  Origin.set(target, new Origin(name, 'default'));

  for (key in target) {
    exportedValue = target[key];

    if (typeof exportedValue === 'function') {
      Origin.set(exportedValue, new Origin(name, key));
    }
  }

  return executed;
}

/**
* A default implementation of the Loader abstraction which works with webpack (extended common-js style).
*/
export class WebpackLoader extends Loader {
  constructor() {
    super();

    this.moduleRegistry = Object.create(null);
    this.loaderPlugins = Object.create(null);
    this.useTemplateLoader(new TextTemplateLoader());

    let that = this;

    this.addPlugin('template-registry-entry', {
      'fetch': function(address) {
        let entry = that.getOrCreateTemplateRegistryEntry(address);
        return entry.templateIsLoaded ? entry : that.templateLoader.loadTemplate(that, entry).then(x => entry);
      }
    });

    PLATFORM.eachModule = callback => {
      let registry = this.moduleRegistry;

      for (let key in registry) {
        try {
          if (callback(key, registry[key])) return;
        } catch (e) {}
      }
    };
  }

  _import(moduleId) {
    const moduleIdParts = moduleId.split('!');
    const path = moduleIdParts.splice(moduleIdParts.length - 1, 1)[0];
    const loaderPlugin = moduleIdParts.length === 1 ? moduleIdParts[0] : null;

    return new Promise((resolve, reject) => {
      try {
        if (loaderPlugin) {
          resolve(this.loaderPlugins[loaderPlugin].fetch(path));
        } else {
          require.ensure([], function(require) {
            const result = require('aurelia-loader-context/' + path);
            if (typeof(result) === 'function') { // because of async loading when the bundle loader is active
              result(res => resolve(res));
            } else {
              resolve(result);
            }
          }, 'app');
        }
      } catch (e) {
        reject(e);
      }
    });
  }

  /**
  * Maps a module id to a source.
  * @param id The module id.
  * @param source The source to map the module to.
  */
  map(id, source) {

  }

  /**
  * Normalizes a module id.
  * @param moduleId The module id to normalize.
  * @param relativeTo What the module id should be normalized relative to.
  * @return The normalized module id.
  */
  normalizeSync(moduleId, relativeTo) {
    return moduleId;
  }

  /**
  * Normalizes a module id.
  * @param moduleId The module id to normalize.
  * @param relativeTo What the module id should be normalized relative to.
  * @return The normalized module id.
  */
  normalize(moduleId, relativeTo) {
    return Promise.resolve(moduleId);
  }

  /**
  * Instructs the loader to use a specific TemplateLoader instance for loading templates
  * @param templateLoader The instance of TemplateLoader to use for loading templates.
  */
  useTemplateLoader(templateLoader) {
    this.templateLoader = templateLoader;
  }

  /**
  * Loads a collection of modules.
  * @param ids The set of module ids to load.
  * @return A Promise for an array of loaded modules.
  */
  loadAllModules(ids) {
    let loads = [];

    for (let i = 0, ii = ids.length; i < ii; ++i) {
      loads.push(this.loadModule(ids[i]));
    }

    return Promise.all(loads);
  }

  /**
  * Loads a module.
  * @param id The module id to normalize.
  * @return A Promise for the loaded module.
  */
  loadModule(id) {
    let existing = this.moduleRegistry[id];
    if (existing) {
      return Promise.resolve(existing);
    }

    return new Promise((resolve, reject) => {
      try {
        this._import(id).then(m => {
          this.moduleRegistry[id] = m;
          resolve(ensureOriginOnExports(m, id));
        });
      } catch (e) {
        reject(e);
      }
    });
  }

  /**
  * Loads a template.
  * @param url The url of the template to load.
  * @return A Promise for a TemplateRegistryEntry containing the template.
  */
  loadTemplate(url) {
    return this._import(this.applyPluginToUrl(url, 'template-registry-entry'));
  }

  /**
  * Loads a text-based resource.
  * @param url The url of the text file to load.
  * @return A Promise for text content.
  */
  loadText(url) {
    return this._import(url).then(result => {
      if (result instanceof Array && result[0] instanceof Array && result.hasOwnProperty('toString')) {
        // we're dealing with a file loaded using the css-loader:
        return result.toString();
      } else {
        return result;
      }
    });
  }

  /**
  * Alters a module id so that it includes a plugin loader.
  * @param url The url of the module to load.
  * @param pluginName The plugin to apply to the module id.
  * @return The plugin-based module id.
  */
  applyPluginToUrl(url, pluginName) {
    return `${pluginName}!${url}`;
  }

  /**
  * Registers a plugin with the loader.
  * @param pluginName The name of the plugin.
  * @param implementation The plugin implementation.
  */
  addPlugin(pluginName, implementation) {
    this.loaderPlugins[pluginName] = implementation;
  }
}

PLATFORM.Loader = WebpackLoader;
<|endoftext|>


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

var http = require('http');
var fs = require('fs');
var path = require('path');
var parseUrl = require('url').parse;

function getUsers() {
    return {
        'secret': 'pass',
        'root': 'root',
        'javi': 'javi',
        'presenter1': 'presenter1',
        'presenter2': 'presenter2',
    }
}

function getPollDefinitions() {
    return [
        {
            id: 0,
            title: 'Talk on something very techie.',
            questions: [
                {
                    type: 'update',
                    title: 'Do you like JS?',
                    id: 0,
                    responses: [
                        {
                            id: 0,
                            text: "Yes! JS is amazing. Best ever!",
                            count: 0
                        },
                        {
                            id: 1,
                            text: "Yes... but it has its pros and cons...",
                            count: 0
                        },
                        {
                            id: 2,
                            text: "Not a lot... coding everywhere with the same language helps.",
                            count: 0
                        },
                        {
                            id: 3,
                            text: "Nope! it is such an ugly language!",
                            count: 0
                        }
                    ]
                },
                {
                    type: 'update',
                    title: 'JS, Python or Java?',
                    id: 1,
                    responses: [
                        {
                            id: 0,
                            text: "JS always!",
                            count: 0
                        },
                        {
                            id: 1,
                            text: "Python, batteries included.",
                            count: 0
                        },
                        {
                            id: 2,
                            text: "Java & XML for the win.",
                            count: 0
                        },
                        {
                            id: 3,
                            text: "Those are toys... I prefer BrainFuck",
                            count: 0
                        }
                    ]
                }
            ]
        }
    ];
}

function authorize(user, pass) {
    console.log('>> user %s trying to login', user);
    presenterId = ""+Date.now();
    var authorized = getUsers()[user] === pass;
    console.log('<< user %s is authorized: %s', user, authorized);
    return authorized;
}

var currentPollId = 0;
var currentPoll = getPollDefinitions()[currentPollId];
var currentQuestion = 0;
var clientResponses = [];
var presenterId = null;
var users = 0;

function getCurrentQuestion() {
    return currentPoll.questions[currentQuestion];
}

function updateClients(responses, object) {
    responses.forEach(function (response) {
        response.write('data: '+JSON.stringify(object)+'\n\n');
    })
}

function httpNotFound(req, res) {
    res.writeHead(404);
    res.end();
}

function decorateWithBody(func, req, res) {
    var body = '';
    req.on('data', function(chunk) {
        body += chunk;
    });

    req.on('end', function() {
        try {
            req.json = JSON.parse(body);
        } catch (err) {}
        req.body = body;
        func(req, res);
    });
    return function(){};

}

var router = {
    getRoutes: [],
    postRoutes: [],
    post: function(path, func) {
        this.postRoutes.push({path: path, func: func})
    },
    get: function(path, func) {
        this.getRoutes.push({path: path, func: func})
    },
    routeFor: function(req, res){
        var route;
        var routesArrName = req.method.toLowerCase()+'Routes';
        var routes = this[routesArrName];
        var funcToApply = this.findMatch(routes, req);
        console.log(' ++ funcToApply', funcToApply.name);
        route = decorateWithBody(funcToApply, req, res);
        return route;
    },
    findMatch: function(routes, req){
        var url = req.parsedUrl.pathname;
        console.log('>>>> req.parsedUrl', url, req.method);
        var route = httpNotFound;
        var currRoute;
        //return this.getRoutes[0].func; //TODO: IMPLEMENT.
        for(var i = 0; i < routes.length; i ++){
            currRoute = routes[i];
            console.log('checking:', url, 'against:', currRoute.path);
            if (currRoute.path === url) {
                console.log('Found route!');
                route = currRoute.func;
                break;
            }
        }
        return route;
    },
    route: function() {
        return function(req, res) {
            req.parsedUrl = parseUrl(req.url, true);
            var func = this.routeFor(req, res);
            func(req, res);
        }.bind(this);
    }
};

function getIndex(req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    var page = fs.readFileSync(path.join(__dirname, 'page.html'), {encoding: 'utf8'});
    var user=req.parsedUrl.query.user;
    var pass=req.parsedUrl.query.pass;

    if (user && pass) {
        if ( authorize(user, pass)) {
            page = page.replace('<!-- presenter-id -->', presenterId);
            //TODO: render an html piece in a page.
            var presenterButtonsHtml = '<input type="button" id="page-prev" class="response-btn" name="prev" value="< prev" onclick="submitPagingRequest(\'prev\')">' +
                '<input type="button" id="page-next" class="response-btn" name="next" value="next >" onclick="submitPagingRequest(\'next\')">';
            page = page.replace('<!-- presenter-sect -->', presenterButtonsHtml);
        } else {
            res.writeHead(401, {'Content-Type': 'text/html'});
            res.end('Unauthorized');
        }
    } else {
        console.log('========>>> generating user page.');
        users += 1;
        page = page.replace('<!-- presenter-id -->', 'happy-user-'+users);
    }

    return res.end(page);
}

function getPoll(req, res) {
    clientResponses.push(res);
    console.log('>>>>', req.url, req.headers);
    res.writeHead(200, {'Content-Type': 'text/event-stream'});
    var currQuestion = getCurrentQuestion();
    console.log('#############################################################currQuestion', currQuestion);
    updateClients(clientResponses, currQuestion);
    return;
}

function postResponse (req, res) {
    var question = getCurrentQuestion();
    var pollResponse = req.json;
    console.log('***** response: question:', pollResponse.question, 'response:', pollResponse.response);
    if(typeof pollResponse.question === 'number' && typeof pollResponse.response === 'number' ) {
        console.log('***** correct response: question:', pollResponse.question, 'response:', pollResponse.response);

        if(question.id === pollResponse.question && pollResponse.response <= question.responses.length - 1) {
            console.log('*** question and response found. counting.');
            question.responses[pollResponse.response].count += 1;
            updateClients(clientResponses, question);
            res.writeHead(200, "OK", {'Content-Type': 'text/html'});
        } else {
            var msg = "Not found: curr question: "+question.id+" max response: "+ question.responses.length -1;
            console.log('*** '+ msg);
            res.writeHead(404, msg, {'Content-Type': 'text/html'});
        }

    } else {
        var msg = ">>> could not parse question correctly :-(";
        console.log('====' + msg);
        res.writeHead(400, msg, {'Content-Type': 'text/html'});
    }
    res.end();

    return;

}

function postPagination(req, res){
    //curl -X POST --data '{"userId": "lksdjflsdkjf"}' http://localhost:8125/prev -v
    //curl -X POST --data '{"userId": "lksdjflsdkjf"}' http://localhost:8125/next -v
    var urlPath = req.parsedUrl.pathname;
    var isPageNextReq = urlPath.indexOf('next') !== -1;
    var isPagePrevReq = urlPath.indexOf('prev') !== -1;

    var pagingRequest = req.json;
    if (pagingRequest.userId !== presenterId) {//TODO: proper auth system.
        var msg = '%%%==> pagingRequest NOT authorized: userId: '+pagingRequest.userId+' presenterId: ' + presenterId;
        console.log(msg, pagingRequest);

        res.writeHead(401, msg, {'Content-Type': 'text/html'});
        return res.end();
    }
    console.log('%%%==> pagingRequest authorized', pagingRequest, urlPath);
    res.writeHead(204, "Done.", {'Content-Type': 'text/html'});
    res.end();
    if(isPageNextReq) {
        console.log('=========>>>>> NEXT');
        var numberOfPolls = currentPoll.questions.length;
        if(currentQuestion < numberOfPolls -1) {
            currentQuestion += 1;
        }
    } else {
        console.log('=========>>>>> PREV');
        if(currentQuestion > 0) {
            currentQuestion -= 1;
        }
    }
    updateClients(clientResponses, getCurrentQuestion());

    return;
}

router.get('/', getIndex);
router.get('/index.html', getIndex);
router.get('/poll', getPoll);
router.post('/response', postResponse);
router.post('/next', postPagination);
router.post('/prev', postPagination);

var server_port = process.env.OPENSHIFT_NODEJS_PORT || 8126;
var server_ip_address = process.env.OPENSHIFT_NODEJS_IP || "0.0.0.0";

http.createServer(router.route()).listen(server_port, server_ip_address);
console.log('Server running at http://'+server_ip_address+':'+server_port+'/');
<commit_msg>initPoll. preparing for poll declaration vs instances<commit_after>#!/usr/bin/env node

var http = require('http');
var fs = require('fs');
var path = require('path');
var parseUrl = require('url').parse;

function getUsers() {
    return {
        'secret': 'pass',
        'root': 'root',
        'javi': 'javi',
        'presenter1': 'presenter1',
        'presenter2': 'presenter2',
    }
}

function getPollDefinitions() {
    return [
        {
            id: 0,
            title: 'Talk on something very techie.',
            questions: [
                {
                    type: 'update',
                    title: 'Do you like JS?',
                    id: 0,
                    responses: [
                        {
                            id: 0,
                            text: "Yes! JS is amazing. Best ever!",
                            count: 0
                        },
                        {
                            id: 1,
                            text: "Yes... but it has its pros and cons...",
                            count: 0
                        },
                        {
                            id: 2,
                            text: "Not a lot... coding everywhere with the same language helps.",
                            count: 0
                        },
                        {
                            id: 3,
                            text: "Nope! it is such an ugly language!",
                            count: 0
                        }
                    ]
                },
                {
                    type: 'update',
                    title: 'JS, Python or Java?',
                    id: 1,
                    responses: [
                        {
                            id: 0,
                            text: "JS always!",
                            count: 0
                        },
                        {
                            id: 1,
                            text: "Python, batteries included.",
                            count: 0
                        },
                        {
                            id: 2,
                            text: "Java & XML for the win.",
                            count: 0
                        },
                        {
                            id: 3,
                            text: "Those are toys... I prefer BrainFuck",
                            count: 0
                        }
                    ]
                }
            ]
        }
    ];
}

function authorize(user, pass) {
    console.log('>> user %s trying to login', user);
    presenterId = ""+Date.now();
    var authorized = getUsers()[user] === pass;
    console.log('<< user %s is authorized: %s', user, authorized);
    return authorized;
}

function initPoll(id){
    return JSON.parse(JSON.stringify(getPollDefinitions()[id]));
}

var currentPollId = 0;
var currentPoll = initPoll(currentPollId);
var currentQuestion = 0;
var clientResponses = [];
var presenterId = null;
var users = 0;

function getCurrentQuestion() {
    return currentPoll.questions[currentQuestion];
}

function updateClients(responses, object) {
    responses.forEach(function (response) {
        response.write('data: '+JSON.stringify(object)+'\n\n');
    })
}

function httpNotFound(req, res) {
    res.writeHead(404);
    res.end();
}

function decorateWithBody(func, req, res) {
    var body = '';
    req.on('data', function(chunk) {
        body += chunk;
    });

    req.on('end', function() {
        try {
            req.json = JSON.parse(body);
        } catch (err) {}
        req.body = body;
        func(req, res);
    });
    return function(){};

}

var router = {
    getRoutes: [],
    postRoutes: [],
    post: function(path, func) {
        this.postRoutes.push({path: path, func: func})
    },
    get: function(path, func) {
        this.getRoutes.push({path: path, func: func})
    },
    routeFor: function(req, res){
        var route;
        var routesArrName = req.method.toLowerCase()+'Routes';
        var routes = this[routesArrName];
        var funcToApply = this.findMatch(routes, req);
        console.log(' ++ funcToApply', funcToApply.name);
        route = decorateWithBody(funcToApply, req, res);
        return route;
    },
    findMatch: function(routes, req){
        var url = req.parsedUrl.pathname;
        console.log('>>>> req.parsedUrl', url, req.method);
        var route = httpNotFound;
        var currRoute;
        //return this.getRoutes[0].func; //TODO: IMPLEMENT.
        for(var i = 0; i < routes.length; i ++){
            currRoute = routes[i];
            console.log('checking:', url, 'against:', currRoute.path);
            if (currRoute.path === url) {
                console.log('Found route!');
                route = currRoute.func;
                break;
            }
        }
        return route;
    },
    route: function() {
        return function(req, res) {
            req.parsedUrl = parseUrl(req.url, true);
            var func = this.routeFor(req, res);
            func(req, res);
        }.bind(this);
    }
};

function getIndex(req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    var page = fs.readFileSync(path.join(__dirname, 'page.html'), {encoding: 'utf8'});
    var user=req.parsedUrl.query.user;
    var pass=req.parsedUrl.query.pass;

    if (user && pass) {
        if ( authorize(user, pass)) {
            page = page.replace('<!-- presenter-id -->', presenterId);
            //TODO: render an html piece in a page.
            var presenterButtonsHtml = '<input type="button" id="page-prev" class="response-btn" name="prev" value="< prev" onclick="submitPagingRequest(\'prev\')">' +
                '<input type="button" id="page-next" class="response-btn" name="next" value="next >" onclick="submitPagingRequest(\'next\')">';
            page = page.replace('<!-- presenter-sect -->', presenterButtonsHtml);
        } else {
            res.writeHead(401, {'Content-Type': 'text/html'});
            res.end('Unauthorized');
        }
    } else {
        console.log('========>>> generating user page.');
        users += 1;
        page = page.replace('<!-- presenter-id -->', 'happy-user-'+users);
    }

    return res.end(page);
}

function getPoll(req, res) {
    clientResponses.push(res);
    console.log('>>>>', req.url, req.headers);
    res.writeHead(200, {'Content-Type': 'text/event-stream'});
    var currQuestion = getCurrentQuestion();
    console.log('#############################################################currQuestion', currQuestion);
    updateClients(clientResponses, currQuestion);
    return;
}

function postResponse (req, res) {
    var question = getCurrentQuestion();
    var pollResponse = req.json;
    console.log('***** response: question:', pollResponse.question, 'response:', pollResponse.response);
    if(typeof pollResponse.question === 'number' && typeof pollResponse.response === 'number' ) {
        console.log('***** correct response: question:', pollResponse.question, 'response:', pollResponse.response);

        if(question.id === pollResponse.question && pollResponse.response <= question.responses.length - 1) {
            console.log('*** question and response found. counting.');
            question.responses[pollResponse.response].count += 1;
            updateClients(clientResponses, question);
            res.writeHead(200, "OK", {'Content-Type': 'text/html'});
        } else {
            var msg = "Not found: curr question: "+question.id+" max response: "+ question.responses.length -1;
            console.log('*** '+ msg);
            res.writeHead(404, msg, {'Content-Type': 'text/html'});
        }

    } else {
        var msg = ">>> could not parse question correctly :-(";
        console.log('====' + msg);
        res.writeHead(400, msg, {'Content-Type': 'text/html'});
    }
    res.end();

    return;

}

function postPagination(req, res){
    //curl -X POST --data '{"userId": "lksdjflsdkjf"}' http://localhost:8125/prev -v
    //curl -X POST --data '{"userId": "lksdjflsdkjf"}' http://localhost:8125/next -v
    var urlPath = req.parsedUrl.pathname;
    var isPageNextReq = urlPath.indexOf('next') !== -1;
    var isPagePrevReq = urlPath.indexOf('prev') !== -1;

    var pagingRequest = req.json;
    if (pagingRequest.userId !== presenterId) {//TODO: proper auth system.
        var msg = '%%%==> pagingRequest NOT authorized: userId: '+pagingRequest.userId+' presenterId: ' + presenterId;
        console.log(msg, pagingRequest);

        res.writeHead(401, msg, {'Content-Type': 'text/html'});
        return res.end();
    }
    console.log('%%%==> pagingRequest authorized', pagingRequest, urlPath);
    res.writeHead(204, "Done.", {'Content-Type': 'text/html'});
    res.end();
    if(isPageNextReq) {
        console.log('=========>>>>> NEXT');
        var numberOfPolls = currentPoll.questions.length;
        if(currentQuestion < numberOfPolls -1) {
            currentQuestion += 1;
        }
    } else {
        console.log('=========>>>>> PREV');
        if(currentQuestion > 0) {
            currentQuestion -= 1;
        }
    }
    updateClients(clientResponses, getCurrentQuestion());

    return;
}

router.get('/', getIndex);
router.get('/index.html', getIndex);
router.get('/poll', getPoll);
router.post('/response', postResponse);
router.post('/next', postPagination);
router.post('/prev', postPagination);

var server_port = process.env.OPENSHIFT_NODEJS_PORT || 8126;
var server_ip_address = process.env.OPENSHIFT_NODEJS_IP || "0.0.0.0";

http.createServer(router.route()).listen(server_port, server_ip_address);
console.log('Server running at http://'+server_ip_address+':'+server_port+'/');
<|endoftext|>


<commit_before>'use strict';

var Snoocore  = require('snoocore');
var fivebeans = require('fivebeans');
var Config    = require('../config');
var twitter   = require('./adapter/twitter');
var reddit    = require('./adapter/reddit');

var bean = new fivebeans.client(Config.beanstalkd.host, Config.beanstalkd.port);
bean.on('connect', function () {
    bean.watch(Config.beanstalkd.tube, function (err) {
        if (err) {
            throw err;
        }
        reserveQueued();
        setInterval(reserveQueued, Config.beanstalkd.delay);
    });
}).on('error', function (err) {
    throw err;
}).connect();

var twitter_counter = 0;

function reserveQueued() {
    try {
        console.log("Reserving beanstalkd job.");
        bean.reserve_with_timeout(1, function (err, job_id, payload) {
            if (typeof job_id == 'undefined') {
                return;
            }
            console.log("Got job #" + job_id);
            var media = JSON.parse(payload);

            reddit.update(media, function() {
                console.log("Reddit has been updated.");

                twitter_counter++;
                if (twitter_counter == 20) {
                    console.log("Updating twitter.");

                    twitter.tweetMedia(media, function (err) {
                        if (err) {
                            console.log(err);
                        } else {
                            console.log("Twitter has been updated.");
                        }
                    });
                    twitter_counter = 0;
                }

                bean.destroy(job_id, function (err) {
                    if (err) {
                        console.log(err);
                    }
                });
            });
        });
    } catch (e) {
        console.log(e);
    }
}<commit_msg>twitter_counter set to 25<commit_after>'use strict';

var Snoocore  = require('snoocore');
var fivebeans = require('fivebeans');
var Config    = require('../config');
var twitter   = require('./adapter/twitter');
var reddit    = require('./adapter/reddit');

var bean = new fivebeans.client(Config.beanstalkd.host, Config.beanstalkd.port);
bean.on('connect', function () {
    bean.watch(Config.beanstalkd.tube, function (err) {
        if (err) {
            throw err;
        }
        reserveQueued();
        setInterval(reserveQueued, Config.beanstalkd.delay);
    });
}).on('error', function (err) {
    throw err;
}).connect();

var twitter_counter = 0;

function reserveQueued() {
    try {
        console.log("Reserving beanstalkd job.");
        bean.reserve_with_timeout(1, function (err, job_id, payload) {
            if (typeof job_id == 'undefined') {
                return;
            }
            console.log("Got job #" + job_id);
            var media = JSON.parse(payload);

            reddit.update(media, function() {
                console.log("Reddit has been updated.");

                twitter_counter++;
                if (twitter_counter == 25) {
                    console.log("Updating twitter.");

                    twitter.tweetMedia(media, function (err) {
                        if (err) {
                            console.log(err);
                        } else {
                            console.log("Twitter has been updated.");
                        }
                    });
                    twitter_counter = 0;
                }

                bean.destroy(job_id, function (err) {
                    if (err) {
                        console.log(err);
                    }
                });
            });
        });
    } catch (e) {
        console.log(e);
    }
}<|endoftext|>


<commit_before>import template from 'babel-template';

const buildClassDecorator = template(`
  DECORATOR(CLASS_REF = INNER) || CLASS_REF;
`);

const buildPropertyDecorator = template(`
  DESC = DECORATOR(TARGET, PROPERTY, INNER) || DESC;
`);

const buildClassPrototype = template(`
  CLASS_REF.prototype;
`);

const buildGetDescriptor = template(`
    Object.getOwnPropertyDescriptor(TARGET, PROPERTY);
`);

const buildSetDescriptor = template(`
    DESC ? Object.defineProperty(TARGET, PROPERTY, DESC) : undefined;
`);

const buildGetObjectInitializer = template(`
    (TEMP = Object.getOwnPropertyDescriptor(TARGET, PROPERTY).value, {
        enumerable: true,
        configurable: true,
        writable: true,
        initializer: function(){
            return TEMP;
        }
    })
`);

const buildSetObjectInitializer = template(`
    (DESC.value = DESC.initializer ? DESC.initializer.call(TARGET) : undefined) &&
        Object.defineProperty(TARGET, PROPERTY, DESC);
`);

const buildGetClassInitializer = template(`
    ({
        enumerable: true,
        configurable: true,
        writable: true,
        initializer: INIT,
    })
`);

const buildSetClassInitializer = template(`
    INIT = DESC.initializer;
`);

export default function({types: t}){

    /**
     * Given a class expression with class-level decorators, create a new expression
     * with the proper decorated behavior.
     */
    function applyClassDecorators(classPath){
        const decorators = classPath.node.decorators || [];
        classPath.node.decorators = null;

        if (decorators.length === 0) return;

        const name = classPath.scope.generateDeclaredUidIdentifier('class');

        return decorators
            .map(dec => dec.expression)
            .reverse()
            .reduce(function(acc, decorator){
                return buildClassDecorator({
                    CLASS_REF: name,
                    DECORATOR: decorator,
                    INNER: acc,
                }).expression;
            }, classPath.node);
    }

    /**
     * Given a class expression with method-level decorators, create a new expression
     * with the proper decorated behavior.
     */
    function applyMethodDecorators(classPath){
        const hasMethodDecorators = classPath.node.body.body.some(function(node){
            return (node.decorators || []).length > 0;
        });

        if (!hasMethodDecorators) return;

        const name = classPath.scope.generateDeclaredUidIdentifier('class');

        return t.sequenceExpression([
            t.assignmentExpression('=', name, classPath.node),
            applyTargetDecorators(classPath, name, classPath.node.body.body),
            name,
        ]);
    }

    /**
     * Given an object expression with property decorators, create a new expression
     * with the proper decorated behavior.
     */
    function applyObjectDecorators(path){
        const hasMethodDecorators = path.node.properties.some(function(node){
            return (node.decorators || []).length > 0;
        });

        if (!hasMethodDecorators) return;

        const name = path.scope.generateDeclaredUidIdentifier('obj');

        return t.sequenceExpression([
            t.assignmentExpression('=', name, path.node),
            applyTargetDecorators(path, name, path.node.properties),
            name,
        ]);
    }

    /**
     * A helper to pull out property decorators into a sequence expression.
     */
    function applyTargetDecorators(path, name, decoratedProps){
        const descName = path.scope.generateDeclaredUidIdentifier('desc');
        const valueTemp = path.scope.generateDeclaredUidIdentifier('value');

        return t.sequenceExpression(decoratedProps.reduce(function(acc, node){
            const decorators = node.decorators || [];
            node.decorators = null;

            if (decorators.length === 0) return acc;

            if (t.isClassProperty(node, {static: true})){
                throw path.buildCodeFrameError('Static class property decorators are not yet supported.')
            }

            if (node.computed){
                throw path.buildCodeFrameError('Computed method/property decorators are not yet supported.')
            }

            const property = t.stringLiteral(node.key.name);

            const target = (path.isClass() && !node.static) ? buildClassPrototype({
                CLASS_REF: name,
            }).expression : name;

            let buildDecoratorCall = function(descriptor, expr){
                return buildPropertyDecorator({
                    TARGET: target,
                    DECORATOR: expr,
                    DESC: descName,
                    INNER: descriptor,
                    PROPERTY: property,
                }).expression;
            };
            let buildDescriptorCreate;
            let buildDescriptorStore;

            if (t.isObjectProperty(node)){
                // Read value from object and store in temp to be read from initializer.
                buildDescriptorCreate = function(){
                    return buildGetObjectInitializer({
                        TEMP: path.scope.generateDeclaredUidIdentifier('init'),
                        TARGET: target,
                        PROPERTY: property
                    }).expression;
                };

                // Trigger initializer and set descriptor.
                buildDescriptorStore = function(){
                    return buildSetObjectInitializer({
                        TARGET: target,
                        PROPERTY: property,
                        DESC: descName,
                    }).expression;
                };
            } else if (t.isClassProperty(node)){
                let init = path.scope.generateDeclaredUidIdentifier('init');
                let oldInitializer;
                if (node.value){
                    // Move the initializer to a temp variable and reassign to new temp.
                    oldInitializer = node.value;
                    acc.push(t.assignmentExpression('=', init, t.functionExpression(null, [], t.blockStatement([
                        t.returnStatement(oldInitializer),
                    ]))));
                }
                node.value = t.callExpression(t.memberExpression(init, t.identifier('apply')), [t.thisExpression()]);

                // Create initializer that reads from new temp.
                buildDescriptorCreate = function(){
                    return buildGetClassInitializer({
                        INIT: oldInitializer ? init : t.nullLiteral(),
                    }).expression;
                };

                // Trigger initializer and set descriptor.
                buildDescriptorStore = function(){
                    return buildSetClassInitializer({
                        INIT: init,
                        DESC: descName,
                    }).expression;
                };
            } else {
                // Read descriptor normally.
                buildDescriptorCreate = function(){
                    return buildGetDescriptor({
                        TARGET: target,
                        PROPERTY: property,
                    }).expression;
                };

                // Store descriptor normally.
                buildDescriptorStore = function(){
                    return buildSetDescriptor({
                        TARGET: target,
                        PROPERTY: property,
                        DESC: descName,
                    }).expression;
                };
            }

            return acc.concat(
                decorators
                    .map(dec => dec.expression)
                    .reverse()
                    .reduce(buildDecoratorCall, t.assignmentExpression('=', descName, buildDescriptorCreate())),
                buildDescriptorStore()
            );
        }, []))
    }

    return {
        inherits: require("babel-plugin-syntax-decorators"),

        visitor: {
            ExportDefaultDeclaration(path){
                if (!path.get("declaration").isClassDeclaration()) return;

                const {node} = path;
                const ref = node.declaration.id || path.scope.generateUidIdentifier("default");
                node.declaration.id = ref;

                // Split the class declaration and the export into two separate statements.
                path.replaceWith(node.declaration);
                path.insertAfter(t.exportNamedDeclaration(null, [t.exportSpecifier(ref, t.identifier('default'))]));
            },
            ClassDeclaration(path){
                const {node} = path;

                const ref = node.id || path.scope.generateUidIdentifier("class");

                path.replaceWith(t.variableDeclaration("let", [
                  t.variableDeclarator(ref, t.toExpression(node))
                ]));
            },
            ClassExpression(path){
                // Create a replacement for the class node if there is one. We do one pass to replace classes with
                // class decorators, and a second pass to process method decorators.
                const decoratedClass = applyClassDecorators(path) || applyMethodDecorators(path);

                if (decoratedClass) path.replaceWith(decoratedClass);
            },
            ObjectExpression(path){
                const decoratedObject = applyObjectDecorators(path);

                if (decoratedObject) path.replaceWith(decoratedObject);
            },
        }
    };
};
<commit_msg>Hoist decorator expressions to ensure ordering after other transforms run.<commit_after>import template from 'babel-template';

const buildClassDecorator = template(`
  DECORATOR(CLASS_REF = INNER) || CLASS_REF;
`);

const buildPropertyDecorator = template(`
  DESC = DECORATOR(TARGET, PROPERTY, INNER) || DESC;
`);

const buildClassPrototype = template(`
  CLASS_REF.prototype;
`);

const buildGetDescriptor = template(`
    Object.getOwnPropertyDescriptor(TARGET, PROPERTY);
`);

const buildSetDescriptor = template(`
    DESC ? Object.defineProperty(TARGET, PROPERTY, DESC) : undefined;
`);

const buildGetObjectInitializer = template(`
    (TEMP = Object.getOwnPropertyDescriptor(TARGET, PROPERTY).value, {
        enumerable: true,
        configurable: true,
        writable: true,
        initializer: function(){
            return TEMP;
        }
    })
`);

const buildSetObjectInitializer = template(`
    (DESC.value = DESC.initializer ? DESC.initializer.call(TARGET) : undefined) &&
        Object.defineProperty(TARGET, PROPERTY, DESC);
`);

const buildGetClassInitializer = template(`
    ({
        enumerable: true,
        configurable: true,
        writable: true,
        initializer: INIT,
    })
`);

const buildSetClassInitializer = template(`
    INIT = DESC.initializer;
`);

export default function({types: t}){

    /**
     * If the decorator expressions are non-identifiers, hoist them to before the class so we can be sure
     * that they are evaluated in order.
     */
    function applyEnsureOrdering(path){
        // TODO: This should probably also hoist computed properties.
        const decorators = (path.isClass() ? [path].concat(path.get('body.body')) : path.get('properties'))
                .reduce((acc, prop) => acc.concat(prop.node.decorators || []), []);

        const identDecorators = decorators.filter(decorator => !t.isIdentifier(decorator.expression));
        if (identDecorators.length === 0) return;

        return t.sequenceExpression(identDecorators.map(decorator => {
            const expression = decorator.expression;
            const id = decorator.expression = path.scope.generateDeclaredUidIdentifier('dec');
            return t.assignmentExpression('=', id, expression);
        }).concat([path.node]));
    }

    /**
     * Given a class expression with class-level decorators, create a new expression
     * with the proper decorated behavior.
     */
    function applyClassDecorators(classPath){
        const decorators = classPath.node.decorators || [];
        classPath.node.decorators = null;

        if (decorators.length === 0) return;

        const name = classPath.scope.generateDeclaredUidIdentifier('class');

        return decorators
            .map(dec => dec.expression)
            .reverse()
            .reduce(function(acc, decorator){
                return buildClassDecorator({
                    CLASS_REF: name,
                    DECORATOR: decorator,
                    INNER: acc,
                }).expression;
            }, classPath.node);
    }

    /**
     * Given a class expression with method-level decorators, create a new expression
     * with the proper decorated behavior.
     */
    function applyMethodDecorators(classPath){
        const hasMethodDecorators = classPath.node.body.body.some(function(node){
            return (node.decorators || []).length > 0;
        });

        if (!hasMethodDecorators) return;

        const name = classPath.scope.generateDeclaredUidIdentifier('class');

        return t.sequenceExpression([
            t.assignmentExpression('=', name, classPath.node),
            applyTargetDecorators(classPath, name, classPath.node.body.body),
            name,
        ]);
    }

    /**
     * Given an object expression with property decorators, create a new expression
     * with the proper decorated behavior.
     */
    function applyObjectDecorators(path){
        const hasMethodDecorators = path.node.properties.some(function(node){
            return (node.decorators || []).length > 0;
        });

        if (!hasMethodDecorators) return;

        const name = path.scope.generateDeclaredUidIdentifier('obj');

        return t.sequenceExpression([
            t.assignmentExpression('=', name, path.node),
            applyTargetDecorators(path, name, path.node.properties),
            name,
        ]);
    }

    /**
     * A helper to pull out property decorators into a sequence expression.
     */
    function applyTargetDecorators(path, name, decoratedProps){
        const descName = path.scope.generateDeclaredUidIdentifier('desc');
        const valueTemp = path.scope.generateDeclaredUidIdentifier('value');

        return t.sequenceExpression(decoratedProps.reduce(function(acc, node){
            const decorators = node.decorators || [];
            node.decorators = null;

            if (decorators.length === 0) return acc;

            if (t.isClassProperty(node, {static: true})){
                throw path.buildCodeFrameError('Static class property decorators are not yet supported.')
            }

            if (node.computed){
                throw path.buildCodeFrameError('Computed method/property decorators are not yet supported.')
            }

            const property = t.stringLiteral(node.key.name);

            const target = (path.isClass() && !node.static) ? buildClassPrototype({
                CLASS_REF: name,
            }).expression : name;

            let buildDecoratorCall = function(descriptor, expr){
                return buildPropertyDecorator({
                    TARGET: target,
                    DECORATOR: expr,
                    DESC: descName,
                    INNER: descriptor,
                    PROPERTY: property,
                }).expression;
            };
            let buildDescriptorCreate;
            let buildDescriptorStore;

            if (t.isObjectProperty(node)){
                // Read value from object and store in temp to be read from initializer.
                buildDescriptorCreate = function(){
                    return buildGetObjectInitializer({
                        TEMP: path.scope.generateDeclaredUidIdentifier('init'),
                        TARGET: target,
                        PROPERTY: property
                    }).expression;
                };

                // Trigger initializer and set descriptor.
                buildDescriptorStore = function(){
                    return buildSetObjectInitializer({
                        TARGET: target,
                        PROPERTY: property,
                        DESC: descName,
                    }).expression;
                };
            } else if (t.isClassProperty(node)){
                let init = path.scope.generateDeclaredUidIdentifier('init');
                let oldInitializer;
                if (node.value){
                    // Move the initializer to a temp variable and reassign to new temp.
                    oldInitializer = node.value;
                    acc.push(t.assignmentExpression('=', init, t.functionExpression(null, [], t.blockStatement([
                        t.returnStatement(oldInitializer),
                    ]))));
                }
                node.value = t.callExpression(t.memberExpression(init, t.identifier('apply')), [t.thisExpression()]);

                // Create initializer that reads from new temp.
                buildDescriptorCreate = function(){
                    return buildGetClassInitializer({
                        INIT: oldInitializer ? init : t.nullLiteral(),
                    }).expression;
                };

                // Trigger initializer and set descriptor.
                buildDescriptorStore = function(){
                    return buildSetClassInitializer({
                        INIT: init,
                        DESC: descName,
                    }).expression;
                };
            } else {
                // Read descriptor normally.
                buildDescriptorCreate = function(){
                    return buildGetDescriptor({
                        TARGET: target,
                        PROPERTY: property,
                    }).expression;
                };

                // Store descriptor normally.
                buildDescriptorStore = function(){
                    return buildSetDescriptor({
                        TARGET: target,
                        PROPERTY: property,
                        DESC: descName,
                    }).expression;
                };
            }

            return acc.concat(
                decorators
                    .map(dec => dec.expression)
                    .reverse()
                    .reduce(buildDecoratorCall, t.assignmentExpression('=', descName, buildDescriptorCreate())),
                buildDescriptorStore()
            );
        }, []))
    }

    return {
        inherits: require("babel-plugin-syntax-decorators"),

        visitor: {
            ExportDefaultDeclaration(path){
                if (!path.get("declaration").isClassDeclaration()) return;

                const {node} = path;
                const ref = node.declaration.id || path.scope.generateUidIdentifier("default");
                node.declaration.id = ref;

                // Split the class declaration and the export into two separate statements.
                path.replaceWith(node.declaration);
                path.insertAfter(t.exportNamedDeclaration(null, [t.exportSpecifier(ref, t.identifier('default'))]));
            },
            ClassDeclaration(path){
                const {node} = path;

                const ref = node.id || path.scope.generateUidIdentifier("class");

                path.replaceWith(t.variableDeclaration("let", [
                  t.variableDeclarator(ref, t.toExpression(node))
                ]));
            },
            ClassExpression(path){
                // Create a replacement for the class node if there is one. We do one pass to replace classes with
                // class decorators, and a second pass to process method decorators.
                const decoratedClass = applyEnsureOrdering(path) || applyClassDecorators(path) || applyMethodDecorators(path);

                if (decoratedClass) path.replaceWith(decoratedClass);
            },
            ObjectExpression(path){
                const decoratedObject = applyEnsureOrdering(path) || applyObjectDecorators(path);

                if (decoratedObject) path.replaceWith(decoratedObject);
            },
        }
    };
};
<|endoftext|>