<commit_before>import { BaseDriver, DeviceSettings } from 'appium-base-driver';
import Chromedriver from 'appium-chromedriver';
import desiredConstraints from './desired-caps';
import commands from './commands/index';
import { setupNewChromedriver } from './commands/context';
import helpers from './android-helpers';
import { CHROMIUM_WIN } from './webview-helpers';
import log from './logger';
import _ from 'lodash';
import { DEFAULT_ADB_PORT } from 'appium-adb';
import { fs, tempDir } from 'appium-support';
import { retryInterval } from 'asyncbox';

const APP_EXTENSION = '.apk';
const DEVICE_PORT = 4724;

// This is a set of methods and paths that we never want to proxy to
// Chromedriver
const NO_PROXY = [
  ['POST', new RegExp('^/session/[^/]+/context')],
  ['GET', new RegExp('^/session/[^/]+/context')],
  ['POST', new RegExp('^/session/[^/]+/appium')],
  ['GET', new RegExp('^/session/[^/]+/appium')],
  ['POST', new RegExp('^/session/[^/]+/touch/perform')],
  ['POST', new RegExp('^/session/[^/]+/touch/multi/perform')],
  ['POST', new RegExp('^/session/[^/]+/orientation')],
  ['GET', new RegExp('^/session/[^/]+/orientation')],
];

class AndroidDriver extends BaseDriver {
  constructor (opts = {}, shouldValidateCaps = true) {
    super(opts, shouldValidateCaps);
    this.locatorStrategies = [
      'xpath',
      'id',
      'class name',
      'accessibility id',
      '-android uiautomator'
    ];
    this.desiredCapConstraints = desiredConstraints;
    this.sessionChromedrivers = {};
    this.jwpProxyActive = false;
    this.jwpProxyAvoid = _.clone(NO_PROXY);
    this.settings = new DeviceSettings({ignoreUnimportantViews: false},
                                       this.onSettingsUpdate.bind(this));
    this.chromedriver = null;
    this.apkStrings = {};
    this.acceptSslCerts = !!opts.acceptSslCerts;
    this.bootstrapPort = opts.bootstrapPort || DEVICE_PORT;
  }

  async createSession (caps) {
    // the whole createSession flow is surrounded in a try-catch statement
    // if creating a session fails at any point, we teardown everything we
    // set up before throwing the error.
    try {
      let sessionId;
      [sessionId] = await super.createSession(caps);

      let serverDetails = {platform: 'LINUX',
                           webStorageEnabled: false,
                           takesScreenshot: true,
                           javascriptEnabled: true,
                           databaseEnabled: false,
                           networkConnectionEnabled: true,
                           locationContextEnabled: false,
                           warnings: {},
                           desired: this.caps};

      this.caps = Object.assign(serverDetails, this.caps);

      // assigning defaults
      let defaultOpts = {
        action: "android.intent.action.MAIN",
        category: "android.intent.category.LAUNCHER",
        flags: "0x10200000",
        disableAndroidWatchers: false,
        tmpDir: await tempDir.staticDir(),
        fullReset: false,
        autoLaunch: true,
        adbPort: DEFAULT_ADB_PORT,
        androidInstallTimeout: 90000
      };
      _.defaults(this.opts, defaultOpts);
      if (!this.opts.javaVersion) {
        this.opts.javaVersion = await helpers.getJavaVersion();
      }

      // not user visible via caps
      if (this.opts.noReset === true) this.opts.fullReset = false;
      if (this.opts.fullReset === true) this.opts.noReset = false;
      this.opts.fastReset = !this.opts.fullReset && !this.opts.noReset;
      this.opts.skipUninstall = this.opts.fastReset || this.opts.noReset;

      this.curContext = this.defaultContextName();

      if (this.isChromeSession) {
        log.info("We're going to run a Chrome-based session");
        let {pkg, activity} = helpers.getChromePkg(this.opts.browserName);
        this.opts.appPackage = pkg;
        this.opts.appActivity = activity;
        log.info(`Chrome-type package and activity are ${pkg} and ${activity}`);
      }

      if (this.opts.nativeWebScreenshot) {
        this.jwpProxyAvoid.push(['GET', new RegExp('^/session/[^/]+/screenshot')]);
      }

      if (this.opts.reboot) {
        this.setAvdFromCapabilities(caps);
        this.addWipeDataToAvdArgs();
      }

      // get device udid for this session
      let {udid, emPort} = await helpers.getDeviceInfoFromCaps(this.opts);
      this.opts.udid = udid;
      this.opts.emPort = emPort;

      // set up an instance of ADB
      this.adb = await helpers.createADB(this.opts.javaVersion, this.opts.udid,
                                         this.opts.emPort,
                                         this.opts.adbPort);

      if (this.helpers.isPackageOrBundle(this.opts.app)){
        // user provided package instead of app for 'app' capability, massage options
        this.opts.appPackage = this.opts.app;
        this.opts.app = null;
      }

      if (this.opts.app) {
        // find and copy, or download and unzip an app url or path
        this.opts.app = await this.helpers.configureApp(this.opts.app, APP_EXTENSION);
        await this.checkAppPresent();
      } else if (this.appOnDevice) {
        // the app isn't an actual app file but rather something we want to
        // assume is on the device and just launch via the appPackage
        log.info(`App file was not listed, instead we're going to run ` +
                 `${this.opts.appPackage} directly on the device`);
        await this.checkPackagePresent();
      }

      await this.startAndroidSession(this.opts);
      return [sessionId, this.caps];
    } catch (e) {
      await this.deleteSession();
      throw e;
    }
  }

  setAvdFromCapabilities (caps) {
    if (this.opts.avd) {
      log.info('avd name defined, ignoring device name and platform version');
    } else {
      if (!caps.deviceName) {
        log.errorAndThrow('avd or deviceName should be specified when reboot option is enables');
      }
      if (!caps.platformVersion) {
        log.errorAndThrow('avd or platformVersion should be specified when reboot option is enabled');
      }
      let avdDevice = caps.deviceName.replace(/[^a-zA-Z0-9_.]/g, "-");
      this.opts.avd = `${avdDevice}__${caps.platformVersion}`;
    }
  }

  addWipeDataToAvdArgs () {
    if (!this.opts.avdArgs) {
      this.opts.avdArgs = '-wipe-data';
    } else  if (this.opts.avdArgs.toLowerCase().indexOf("-wipe-data") === -1) {
      this.opts.avdArgs += ' -wipe-data';
    }
  }

  get appOnDevice () {
    return this.helpers.isPackageOrBundle(this.opts.app) || (!this.opts.app &&
           this.helpers.isPackageOrBundle(this.opts.appPackage));
  }

  get isChromeSession () {
    return helpers.isChromeBrowser(this.opts.browserName);
  }

  async onSettingsUpdate (key, value) {
    if (key === "ignoreUnimportantViews") {
      await this.setCompressedLayoutHierarchy(value);
    }
  }

  async startAndroidSession () {
    log.info(`Starting Android session`);
    // set up the device to run on (real or emulator, etc)
    this.defaultIME = await helpers.initDevice(this.adb, this.opts);

    // set actual device name, udid & platform version
    this.caps.deviceName = this.adb.curDeviceId;
    this.caps.deviceUDID = this.opts.udid;
    this.caps.platformVersion = await this.adb.getPlatformVersion();

    // Let's try to unlock before installing the app
    // unlock the device
    await helpers.unlock(this.adb);
    // If the user sets autoLaunch to false, they are responsible for initAUT() and startAUT()
    if (this.opts.autoLaunch) {
      // set up app under test
      await this.initAUT();
    }
    // start UiAutomator
    this.bootstrap = new helpers.bootstrap(this.adb, this.bootstrapPort, this.opts.websocket);
    await this.bootstrap.start(this.opts.appPackage, this.opts.disableAndroidWatchers);
    // handling unexpected shutdown
    this.bootstrap.onUnexpectedShutdown.catch(async (err) => {
      if (!this.bootstrap.ignoreUnexpectedShutdown) {
        await this.startUnexpectedShutdown(err);
      }
    });

    // Set CompressedLayoutHierarchy on the device based on current settings object
    // this has to happen _after_ bootstrap is initialized
    if (this.opts.ignoreUnimportantViews) {
      await this.settings.update({ignoreUnimportantViews: this.opts.ignoreUnimportantViews});
    }

    if (this.isChromeSession) {
      // start a chromedriver session and proxy to it
      await this.startChromeSession();
    } else {
      if (this.opts.autoLaunch) {
        // start app
        await this.startAUT();
      }
    }
    await this.initAutoWebview();
  }

  async initAutoWebview () {
    if (this.opts.autoWebview) {
      let viewName = this.defaultWebviewName();
      let timeout = (this.opts.autoWebviewTimeout) || 2000;

      log.info(`Setting auto webview to context '${viewName}' with timeout ${timeout}ms`);

      // try every 500ms until timeout is over
      await retryInterval(timeout / 500, 500, async () => {
        await this.setContext(viewName);
      });
    }
  }

  async initAUT () {
    // populate appPackage, appActivity, appWaitPackage, appWaitActivity,
    // and the device being used
    // in the opts and caps (so it gets back to the user on session creation)
    let launchInfo = await helpers.getLaunchInfo(this.adb, this.opts);
    Object.assign(this.opts, launchInfo);
    Object.assign(this.caps, launchInfo);
    // install app
    if (!this.opts.app) {
      if (this.opts.fullReset) {
        log.errorAndThrow('Full reset requires an app capability, use fastReset if app is not provided');
      }
      log.debug('No app capability. Assuming it is already on the device');
      if (this.opts.fastReset) {
        await helpers.resetApp(this.adb, this.opts.app, this.opts.appPackage, this.opts.fastReset);
      }
      return;
    }
    if (!this.opts.skipUninstall) {
      await this.adb.uninstallApk(this.opts.appPackage);
    }
    await helpers.installApkRemotely(this.adb, this.opts);
    this.apkStrings[this.opts.language] = await helpers.pushStrings(
        this.opts.language, this.adb, this.opts);
  }

  async startChromeSession () {
    log.info("Starting a chrome-based browser session");
    let opts = _.cloneDeep(this.opts);
    opts.chromeUseRunningApp = false;

    const knownPackages = ["org.chromium.chrome.shell",
                           "com.android.chrome",
                           "com.chrome.beta",
                           "org.chromium.chrome"];

    if (!_.contains(knownPackages, this.opts.appPackage)) {
      opts.chromeAndroidActivity = this.opts.appActivity;
    }
    this.chromedriver = await setupNewChromedriver(opts, this.adb.curDeviceId,
                                                   this.adb.getAdbServerPort());
    this.chromedriver.on(Chromedriver.EVENT_CHANGED, (msg) => {
      if (msg.state === Chromedriver.STATE_STOPPED) {
        this.onChromedriverStop(CHROMIUM_WIN);
      }
    });

    // Now that we have a Chrome session, we ensure that the context is
    // appropriately set and that this chromedriver is added to the list
    // of session chromedrivers so we can switch back and forth
    this.curContext = CHROMIUM_WIN;
    this.sessionChromedrivers[CHROMIUM_WIN] = this.chromedriver;
    this.proxyReqRes = this.chromedriver.proxyReq.bind(this.chromedriver);
    this.jwpProxyActive = true;
  }

  async checkAppPresent () {
    log.debug("Checking whether app is actually present");
    if (!(await fs.exists(this.opts.app))) {
      log.errorAndThrow(`Could not find app apk at ${this.opts.app}`);
    }
  }

  async checkPackagePresent () {
    log.debug("Checking whether package is present on the device");
    if (!(await this.adb.shell(['pm', 'list', 'packages', this.opts.appPackage]))) {
      log.errorAndThrow(`Could not find package ${this.opts.appPackage} on the device`);
    }
  }

  // Set CompressedLayoutHierarchy on the device
  async setCompressedLayoutHierarchy (compress) {
    await this.bootstrap.sendAction("compressedLayoutHierarchy", {compressLayout: compress});
  }

  async deleteSession () {
    log.debug("Shutting down Android driver");
    await super.deleteSession();
    if (this.bootstrap) {
      // certain cleanup we only care to do if the bootstrap was ever run
      await this.stopChromedriverProxies();
      if (this.opts.unicodeKeyboard && this.opts.resetKeyboard && this.defaultIME) {
        log.debug(`Resetting IME to ${this.defaultIME}`);
        await this.adb.setIME(this.defaultIME);
      }
      if (!this.isChromeSession) {
        await this.adb.forceStop(this.opts.appPackage);
      }
      await this.adb.goToHome();
      if (this.opts.fullReset && !this.opts.skipUninstall && !this.appOnDevice) {
        await this.adb.uninstallApk(this.opts.appPackage);
      }
      await this.bootstrap.shutdown();
      if (this.opts.reboot) {
        let avdName = this.opts.avd.replace('@', '');
        log.debug(`closing emulator '${avdName}'`);
        this.adb.killEmulator(avdName);
      }
      this.bootstrap = null;
    } else {
      log.debug("Called deleteSession but bootstrap wasn't active");
    }
    // some cleanup we want to do regardless, in case we are shutting down
    // mid-startup
    await this.adb.forceStop('io.appium.unlock');
    await this.adb.stopLogcat();
  }

  validateDesiredCaps (caps) {
    // check with the base class, and return if it fails
    let res = super.validateDesiredCaps(caps);
    if (!res) return res;

    // make sure that the capabilities have one of `app`, `appPackage` or `browser`
    if ((!caps.browserName || !helpers.isChromeBrowser(caps.browserName)) &&
      !caps.app && !caps.appPackage) {
      let msg = 'The desired capabilities must include either an app, package or browser';
      log.errorAndThrow(msg);
    }
    // warn if the capabilities have both `app` and `browser, although this
    // is common with selenium grid
    if (caps.browserName && caps.app) {
      let msg = 'The desired capabilities should generally not include both an app and a browser';
      log.warn(msg);
    }
  }

  proxyActive (sessionId) {
    super.proxyActive(sessionId);

    return this.jwpProxyActive;
  }

  getProxyAvoidList (sessionId) {
    super.getProxyAvoidList(sessionId);

    return this.jwpProxyAvoid;
  }

  canProxy (sessionId) {
    super.canProxy(sessionId);

    // this will change depending on ChromeDriver status
    return _.isFunction(this.proxyReqRes);
  }
}

for (let [cmd, fn] of _.pairs(commands)) {
  AndroidDriver.prototype[cmd] = fn;
}

export default AndroidDriver;
<commit_msg>throw real error instead of deleteSession error<commit_after>import { BaseDriver, DeviceSettings } from 'appium-base-driver';
import Chromedriver from 'appium-chromedriver';
import desiredConstraints from './desired-caps';
import commands from './commands/index';
import { setupNewChromedriver } from './commands/context';
import helpers from './android-helpers';
import { CHROMIUM_WIN } from './webview-helpers';
import log from './logger';
import _ from 'lodash';
import { DEFAULT_ADB_PORT } from 'appium-adb';
import { fs, tempDir } from 'appium-support';
import { retryInterval } from 'asyncbox';

const APP_EXTENSION = '.apk';
const DEVICE_PORT = 4724;

// This is a set of methods and paths that we never want to proxy to
// Chromedriver
const NO_PROXY = [
  ['POST', new RegExp('^/session/[^/]+/context')],
  ['GET', new RegExp('^/session/[^/]+/context')],
  ['POST', new RegExp('^/session/[^/]+/appium')],
  ['GET', new RegExp('^/session/[^/]+/appium')],
  ['POST', new RegExp('^/session/[^/]+/touch/perform')],
  ['POST', new RegExp('^/session/[^/]+/touch/multi/perform')],
  ['POST', new RegExp('^/session/[^/]+/orientation')],
  ['GET', new RegExp('^/session/[^/]+/orientation')],
];

class AndroidDriver extends BaseDriver {
  constructor (opts = {}, shouldValidateCaps = true) {
    super(opts, shouldValidateCaps);
    this.locatorStrategies = [
      'xpath',
      'id',
      'class name',
      'accessibility id',
      '-android uiautomator'
    ];
    this.desiredCapConstraints = desiredConstraints;
    this.sessionChromedrivers = {};
    this.jwpProxyActive = false;
    this.jwpProxyAvoid = _.clone(NO_PROXY);
    this.settings = new DeviceSettings({ignoreUnimportantViews: false},
                                       this.onSettingsUpdate.bind(this));
    this.chromedriver = null;
    this.apkStrings = {};
    this.acceptSslCerts = !!opts.acceptSslCerts;
    this.bootstrapPort = opts.bootstrapPort || DEVICE_PORT;
  }

  async createSession (caps) {
    // the whole createSession flow is surrounded in a try-catch statement
    // if creating a session fails at any point, we teardown everything we
    // set up before throwing the error.
    try {
      let sessionId;
      [sessionId] = await super.createSession(caps);

      let serverDetails = {platform: 'LINUX',
                           webStorageEnabled: false,
                           takesScreenshot: true,
                           javascriptEnabled: true,
                           databaseEnabled: false,
                           networkConnectionEnabled: true,
                           locationContextEnabled: false,
                           warnings: {},
                           desired: this.caps};

      this.caps = Object.assign(serverDetails, this.caps);

      // assigning defaults
      let defaultOpts = {
        action: "android.intent.action.MAIN",
        category: "android.intent.category.LAUNCHER",
        flags: "0x10200000",
        disableAndroidWatchers: false,
        tmpDir: await tempDir.staticDir(),
        fullReset: false,
        autoLaunch: true,
        adbPort: DEFAULT_ADB_PORT,
        androidInstallTimeout: 90000
      };
      _.defaults(this.opts, defaultOpts);
      if (!this.opts.javaVersion) {
        this.opts.javaVersion = await helpers.getJavaVersion();
      }

      // not user visible via caps
      if (this.opts.noReset === true) this.opts.fullReset = false;
      if (this.opts.fullReset === true) this.opts.noReset = false;
      this.opts.fastReset = !this.opts.fullReset && !this.opts.noReset;
      this.opts.skipUninstall = this.opts.fastReset || this.opts.noReset;

      this.curContext = this.defaultContextName();

      if (this.isChromeSession) {
        log.info("We're going to run a Chrome-based session");
        let {pkg, activity} = helpers.getChromePkg(this.opts.browserName);
        this.opts.appPackage = pkg;
        this.opts.appActivity = activity;
        log.info(`Chrome-type package and activity are ${pkg} and ${activity}`);
      }

      if (this.opts.nativeWebScreenshot) {
        this.jwpProxyAvoid.push(['GET', new RegExp('^/session/[^/]+/screenshot')]);
      }

      if (this.opts.reboot) {
        this.setAvdFromCapabilities(caps);
        this.addWipeDataToAvdArgs();
      }

      // get device udid for this session
      let {udid, emPort} = await helpers.getDeviceInfoFromCaps(this.opts);
      this.opts.udid = udid;
      this.opts.emPort = emPort;

      // set up an instance of ADB
      this.adb = await helpers.createADB(this.opts.javaVersion, this.opts.udid,
                                         this.opts.emPort,
                                         this.opts.adbPort);

      if (this.helpers.isPackageOrBundle(this.opts.app)){
        // user provided package instead of app for 'app' capability, massage options
        this.opts.appPackage = this.opts.app;
        this.opts.app = null;
      }

      if (this.opts.app) {
        // find and copy, or download and unzip an app url or path
        this.opts.app = await this.helpers.configureApp(this.opts.app, APP_EXTENSION);
        await this.checkAppPresent();
      } else if (this.appOnDevice) {
        // the app isn't an actual app file but rather something we want to
        // assume is on the device and just launch via the appPackage
        log.info(`App file was not listed, instead we're going to run ` +
                 `${this.opts.appPackage} directly on the device`);
        await this.checkPackagePresent();
      }

      await this.startAndroidSession(this.opts);
      return [sessionId, this.caps];
    } catch (e) {
      // ignoring delete session exception if any and throw the real error
      // that happened while creating the session.
      try {
        await this.deleteSession();
      } catch (ign) {}
      throw e;
    }
  }

  setAvdFromCapabilities (caps) {
    if (this.opts.avd) {
      log.info('avd name defined, ignoring device name and platform version');
    } else {
      if (!caps.deviceName) {
        log.errorAndThrow('avd or deviceName should be specified when reboot option is enables');
      }
      if (!caps.platformVersion) {
        log.errorAndThrow('avd or platformVersion should be specified when reboot option is enabled');
      }
      let avdDevice = caps.deviceName.replace(/[^a-zA-Z0-9_.]/g, "-");
      this.opts.avd = `${avdDevice}__${caps.platformVersion}`;
    }
  }

  addWipeDataToAvdArgs () {
    if (!this.opts.avdArgs) {
      this.opts.avdArgs = '-wipe-data';
    } else  if (this.opts.avdArgs.toLowerCase().indexOf("-wipe-data") === -1) {
      this.opts.avdArgs += ' -wipe-data';
    }
  }

  get appOnDevice () {
    return this.helpers.isPackageOrBundle(this.opts.app) || (!this.opts.app &&
           this.helpers.isPackageOrBundle(this.opts.appPackage));
  }

  get isChromeSession () {
    return helpers.isChromeBrowser(this.opts.browserName);
  }

  async onSettingsUpdate (key, value) {
    if (key === "ignoreUnimportantViews") {
      await this.setCompressedLayoutHierarchy(value);
    }
  }

  async startAndroidSession () {
    log.info(`Starting Android session`);
    // set up the device to run on (real or emulator, etc)
    this.defaultIME = await helpers.initDevice(this.adb, this.opts);

    // set actual device name, udid & platform version
    this.caps.deviceName = this.adb.curDeviceId;
    this.caps.deviceUDID = this.opts.udid;
    this.caps.platformVersion = await this.adb.getPlatformVersion();

    // Let's try to unlock before installing the app
    // unlock the device
    await helpers.unlock(this.adb);
    // If the user sets autoLaunch to false, they are responsible for initAUT() and startAUT()
    if (this.opts.autoLaunch) {
      // set up app under test
      await this.initAUT();
    }
    // start UiAutomator
    this.bootstrap = new helpers.bootstrap(this.adb, this.bootstrapPort, this.opts.websocket);
    await this.bootstrap.start(this.opts.appPackage, this.opts.disableAndroidWatchers);
    // handling unexpected shutdown
    this.bootstrap.onUnexpectedShutdown.catch(async (err) => {
      if (!this.bootstrap.ignoreUnexpectedShutdown) {
        await this.startUnexpectedShutdown(err);
      }
    });

    // Set CompressedLayoutHierarchy on the device based on current settings object
    // this has to happen _after_ bootstrap is initialized
    if (this.opts.ignoreUnimportantViews) {
      await this.settings.update({ignoreUnimportantViews: this.opts.ignoreUnimportantViews});
    }

    if (this.isChromeSession) {
      // start a chromedriver session and proxy to it
      await this.startChromeSession();
    } else {
      if (this.opts.autoLaunch) {
        // start app
        await this.startAUT();
      }
    }
    await this.initAutoWebview();
  }

  async initAutoWebview () {
    if (this.opts.autoWebview) {
      let viewName = this.defaultWebviewName();
      let timeout = (this.opts.autoWebviewTimeout) || 2000;

      log.info(`Setting auto webview to context '${viewName}' with timeout ${timeout}ms`);

      // try every 500ms until timeout is over
      await retryInterval(timeout / 500, 500, async () => {
        await this.setContext(viewName);
      });
    }
  }

  async initAUT () {
    // populate appPackage, appActivity, appWaitPackage, appWaitActivity,
    // and the device being used
    // in the opts and caps (so it gets back to the user on session creation)
    let launchInfo = await helpers.getLaunchInfo(this.adb, this.opts);
    Object.assign(this.opts, launchInfo);
    Object.assign(this.caps, launchInfo);
    // install app
    if (!this.opts.app) {
      if (this.opts.fullReset) {
        log.errorAndThrow('Full reset requires an app capability, use fastReset if app is not provided');
      }
      log.debug('No app capability. Assuming it is already on the device');
      if (this.opts.fastReset) {
        await helpers.resetApp(this.adb, this.opts.app, this.opts.appPackage, this.opts.fastReset);
      }
      return;
    }
    if (!this.opts.skipUninstall) {
      await this.adb.uninstallApk(this.opts.appPackage);
    }
    await helpers.installApkRemotely(this.adb, this.opts);
    this.apkStrings[this.opts.language] = await helpers.pushStrings(
        this.opts.language, this.adb, this.opts);
  }

  async startChromeSession () {
    log.info("Starting a chrome-based browser session");
    let opts = _.cloneDeep(this.opts);
    opts.chromeUseRunningApp = false;

    const knownPackages = ["org.chromium.chrome.shell",
                           "com.android.chrome",
                           "com.chrome.beta",
                           "org.chromium.chrome"];

    if (!_.contains(knownPackages, this.opts.appPackage)) {
      opts.chromeAndroidActivity = this.opts.appActivity;
    }
    this.chromedriver = await setupNewChromedriver(opts, this.adb.curDeviceId,
                                                   this.adb.getAdbServerPort());
    this.chromedriver.on(Chromedriver.EVENT_CHANGED, (msg) => {
      if (msg.state === Chromedriver.STATE_STOPPED) {
        this.onChromedriverStop(CHROMIUM_WIN);
      }
    });

    // Now that we have a Chrome session, we ensure that the context is
    // appropriately set and that this chromedriver is added to the list
    // of session chromedrivers so we can switch back and forth
    this.curContext = CHROMIUM_WIN;
    this.sessionChromedrivers[CHROMIUM_WIN] = this.chromedriver;
    this.proxyReqRes = this.chromedriver.proxyReq.bind(this.chromedriver);
    this.jwpProxyActive = true;
  }

  async checkAppPresent () {
    log.debug("Checking whether app is actually present");
    if (!(await fs.exists(this.opts.app))) {
      log.errorAndThrow(`Could not find app apk at ${this.opts.app}`);
    }
  }

  async checkPackagePresent () {
    log.debug("Checking whether package is present on the device");
    if (!(await this.adb.shell(['pm', 'list', 'packages', this.opts.appPackage]))) {
      log.errorAndThrow(`Could not find package ${this.opts.appPackage} on the device`);
    }
  }

  // Set CompressedLayoutHierarchy on the device
  async setCompressedLayoutHierarchy (compress) {
    await this.bootstrap.sendAction("compressedLayoutHierarchy", {compressLayout: compress});
  }

  async deleteSession () {
    log.debug("Shutting down Android driver");
    await super.deleteSession();
    if (this.bootstrap) {
      // certain cleanup we only care to do if the bootstrap was ever run
      await this.stopChromedriverProxies();
      if (this.opts.unicodeKeyboard && this.opts.resetKeyboard && this.defaultIME) {
        log.debug(`Resetting IME to ${this.defaultIME}`);
        await this.adb.setIME(this.defaultIME);
      }
      if (!this.isChromeSession) {
        await this.adb.forceStop(this.opts.appPackage);
      }
      await this.adb.goToHome();
      if (this.opts.fullReset && !this.opts.skipUninstall && !this.appOnDevice) {
        await this.adb.uninstallApk(this.opts.appPackage);
      }
      await this.bootstrap.shutdown();
      if (this.opts.reboot) {
        let avdName = this.opts.avd.replace('@', '');
        log.debug(`closing emulator '${avdName}'`);
        this.adb.killEmulator(avdName);
      }
      this.bootstrap = null;
    } else {
      log.debug("Called deleteSession but bootstrap wasn't active");
    }
    // some cleanup we want to do regardless, in case we are shutting down
    // mid-startup
    await this.adb.forceStop('io.appium.unlock');
    await this.adb.stopLogcat();
  }

  validateDesiredCaps (caps) {
    // check with the base class, and return if it fails
    let res = super.validateDesiredCaps(caps);
    if (!res) return res;

    // make sure that the capabilities have one of `app`, `appPackage` or `browser`
    if ((!caps.browserName || !helpers.isChromeBrowser(caps.browserName)) &&
      !caps.app && !caps.appPackage) {
      let msg = 'The desired capabilities must include either an app, package or browser';
      log.errorAndThrow(msg);
    }
    // warn if the capabilities have both `app` and `browser, although this
    // is common with selenium grid
    if (caps.browserName && caps.app) {
      let msg = 'The desired capabilities should generally not include both an app and a browser';
      log.warn(msg);
    }
  }

  proxyActive (sessionId) {
    super.proxyActive(sessionId);

    return this.jwpProxyActive;
  }

  getProxyAvoidList (sessionId) {
    super.getProxyAvoidList(sessionId);

    return this.jwpProxyAvoid;
  }

  canProxy (sessionId) {
    super.canProxy(sessionId);

    // this will change depending on ChromeDriver status
    return _.isFunction(this.proxyReqRes);
  }
}

for (let [cmd, fn] of _.pairs(commands)) {
  AndroidDriver.prototype[cmd] = fn;
}

export default AndroidDriver;
<|endoftext|>


<commit_before>import React from 'react'
import App from './App/App.jsx'
import request from 'superagent-bluebird-promise'
import Router, {Route} from 'react-router'
import ProposalList from './ProposalList/ProposalList.jsx'

import './normalize.css'
import './index.css'

var scripts = document.getElementsByTagName("script");
var src = scripts[scripts.length - 1].getAttribute("src");
window.__webpack_public_path__ = src.substr(0, src.lastIndexOf("/") + 1);

const routes = (
  <Route handler={App} path="/">
    <Route name="crowdfunding" handler={ProposalList} />
  </Route>
);

var root = document.getElementById('root');
Router.run(routes, Router.HashLocation, (Root) => {
  React.render(<Root/>, root);
});
<commit_msg>update routing<commit_after>import React from 'react'
import App from './App/App.jsx'
import request from 'superagent-bluebird-promise'
import Router, {Route} from 'react-router'
import ProposalList from './ProposalList/ProposalList.jsx'
import Proposal from './Proposal/Proposal.jsx'

import './normalize.css'
import './index.css'

var scripts = document.getElementsByTagName("script");
var src = scripts[scripts.length - 1].getAttribute("src");
window.__webpack_public_path__ = src.substr(0, src.lastIndexOf("/") + 1);

const routes = (
  <Route handler={App} path="/">
    <Route name="proposals" path="proposals"              handler={ProposalList} />
    <Route name="proposal"  path="proposal/:proposalName" handler={Proposal} />
  </Route>
);

var root = document.getElementById('root');
Router.run(routes, Router.HashLocation, (Root) => {
  React.render(<Root/>, root);
});
<|endoftext|>


<commit_before>/**
 * A helper module for AngularUI Router, which allows you to handle redirect chains
 */
(function() {

  'use strict';

  angular.module('ui.router.redirect', ['ui.router'])
  .provider('$redirect', redirectProvider);

  redirectProvider.$inject = ['$urlRouterProvider'];
  function redirectProvider($urlRouterProvider) {

    var otherwiseCallback = angular.noop,
        notFoundCallback = angular.noop,
        debug = false;

    Redirect.$inject = ['$rootScope', '$state', '$q', '$timeout', '$injector'];

    return {
      otherwise: otherwise,
      notFound: notFound,
      setDebug: setDebug,
      $get: Redirect
    };

    /**
     * Callback if the redirection was rejected
     * @params {function} callback
     */
    function otherwise(callback) {
      otherwiseCallback = callback;
    }

    /**
     * Callback if the state was not found
     * @params {function} callback
     */
    function notFound(callback) {
      notFoundCallback = callback;
      $urlRouterProvider.otherwise(callback);
    }

    /**
     * Turn on/off the debugger
     * @params {boolean} debug
     */
    function setDebug(_debug) {
      debug = !!_debug;
    }

    function Redirect($rootScope, $state, $q, $timeout, $injector) {
      var callbackQueue = [],
          redirectQueue = [],
          redirectAccepted = false,
          callIndex = 0,
          cache = {},
          redirectScope = {
            add: add,
            _go: go,
            set: set,
            get: get
          };

      $rootScope.$on('$stateChangeStart', stateChangeStart);

      return redirectScope;

      /**
       * Approve / Deny the ui-router's change
       */
      function stateChangeStart(event, toState, toParams, fromState, fromParams, options) {
        //Initiate checks
        if(!redirectAccepted) {
          event.preventDefault();
          $rootScope.$broadcast('$redirectStart', toState, toParams);
          go({
            name: toState.name,
            params: toParams,
            //Fallback for older versions
            options: options || {}
          });
        }
      }

      /**
       * Define new redirect handler
       * @params callback {function}
       * Redirect handler. First param is the targeted route.
       * Possible options to return:
       *
       * Basic options:
       * false {boolean}: Deny the change
       * true {boolean}: Approve the change
       * route {object}: Redirect to this route
       *
       * promise {object}:
       *    Resolve with a basic option
       *    Reject: Deny the change
       */
      function add() {
        var callback = angular.noop,
            condition = '';

        if(arguments.length === 1) {
          callback = arguments[0];
        } else if(arguments.length === 2) {
          condition = arguments[0];
          callback = arguments[1];
        }

        if(!angular.isString(condition)) {
            throw new Error('Condition should be string');
        }

        if(!angular.isFunction(callback)) {
            throw new Error('Callback should be function');
        }

        callbackQueue.push({
          condition: condition,
          callback: callback
        });

        return this;
      }

      /**
       * Initiate the redirection of the specified route
       * @params {object} route
       * name
       * params
       * options
       */
      function go(route) {
        var deferred = $q.defer();
        //Doesn't exist in the redirect chain
        if(!isProcessed(route)) {
          redirectQueue = [];
          var result = checkRedirectQueue(route, ++callIndex);
          if(!result) {
            deferred.reject();
          } else if(result.then) {
            return result;
          } else {
            deferred.resolve(result);
          }
        } else {
          deferred.resolve(true);
        }
        return deferred.promise;
      }

      /**
       * Cache setter
       * @params {string} key
       * @params {mixed} value
       */
      function set(key, value) {
        cache[key] = value;
        return cache[key];
      }

      /**
       * Cache getter
       * @params {string} key
       */
      function get(key) {
        return cache[key];
      }

      /**
       * Check the targeted route in the redirect chain
       * @params {object} route
       */
      function isProcessed(route) {
        var i = 0,
            redirectQueueLength = redirectQueue.length;

        for(; i < redirectQueueLength; i++) {
          if(angular.equals(route, redirectQueue[i])) {
            return true;
          }
        }

        return false;
      }

      /**
       * New item into the redirect chain
       * @params {object} route
       */
      function addToRedirectQueue(route) {
        var i = 0,
            redirectQueueLength = redirectQueue.length,
            _route = angular.copy(route);

        if(debug) {
          console.info('Redirect to:', _route);
        }

        for(; i < redirectQueueLength; i++) {
          if(angular.equals(_route, redirectQueue[i])) {
            console.info('Redirect queue:', redirectQueue);
            throw new Error('Infinite redirect loop');
          }
        }

        redirectQueue.push(_route);
      }

      /**
       * Check every redirect function and resolve them
       * @params {object} route
       * @params {object} _callIndex
       * Private callcounter
       */
      function checkRedirectQueue(route, _callIndex) {
        var i = 0,
            result = true,
            check = function(route, ignoreRedirectQueue) {

              route.params = route.params || {};
              route.options = route.options || {};

              //New redirect was triggered - cancel the current one
              if(callIndex !== _callIndex) {
                return false;
              }

              //Approved and every callback was checked
              if(i === callbackQueue.length) {
                if(route && route.name) {
                  _go(route);
                }
                return route;
              }

              if(!ignoreRedirectQueue) {
                addToRedirectQueue(route);
              }

              var condition = callbackQueue[i].condition,
                  callback = callbackQueue[i].callback;

              if(
                !condition ||
                new RegExp('^' + condition + '$').test(route.name)
              ) {
                result = callback.call(redirectScope, route);
              } else {
                //Approved - next callback
                result = true;
              }

              return handle(route, result);
            },
            handle = function(route, result) {
              //Promise
              if(result && result.then) {
                var deferred = $q.defer();
                result.then(function(_result) {
                  deferred.resolve(handle(route, _result));
                }, function(_result) {
                  $timeout(function() {
                    otherwiseCallback($injector);
                  });
                  deferred.reject();
                });
                return deferred.promise;
              //Redirect
              } else if(result && result.name) {
                return checkRedirectQueue(result, _callIndex);
              //Approved - next callback
              } else if(result === true) {
                i++;
                return check(route, true);
              //Don't go
              } else if(!result) {
                reset();
                $timeout(function() {
                  otherwiseCallback($injector);
                });
                return false;
              //Wrong format
              } else {
                throw new Error('Wrong redirect format');
              }
            };

        return check(route);
      }

      /**
       * Reset the service
       */
      function reset() {
        redirectQueue = [];
        callIndex = 0;
      }

      /**
       * Enable and redirect
       * @param {object} route
       */
      function _go(route) {
        var stateFound = $state.get(route.name);

        reset();

        if(!stateFound) {
          $rootScope.$broadcast('$redirectNotFound', route);
        }
        if(!stateFound && notFoundCallback !== angular.noop) {
          notFoundCallback($injector);
        } else {
          redirectAccepted = true;
          $state.go(route.name, route.params, route.options);
          redirectAccepted = false;
        }
      }

    }

  }

})();
<commit_msg>fix(route.options): angular.copy error<commit_after>/**
 * A helper module for AngularUI Router, which allows you to handle redirect chains
 */
(function() {

  'use strict';

  angular.module('ui.router.redirect', ['ui.router'])
  .provider('$redirect', redirectProvider);

  redirectProvider.$inject = ['$urlRouterProvider'];
  function redirectProvider($urlRouterProvider) {

    var otherwiseCallback = angular.noop,
        notFoundCallback = angular.noop,
        debug = false;

    Redirect.$inject = ['$rootScope', '$state', '$q', '$timeout', '$injector'];

    return {
      otherwise: otherwise,
      notFound: notFound,
      setDebug: setDebug,
      $get: Redirect
    };

    /**
     * Callback if the redirection was rejected
     * @params {function} callback
     */
    function otherwise(callback) {
      otherwiseCallback = callback;
    }

    /**
     * Callback if the state was not found
     * @params {function} callback
     */
    function notFound(callback) {
      notFoundCallback = callback;
      $urlRouterProvider.otherwise(callback);
    }

    /**
     * Turn on/off the debugger
     * @params {boolean} debug
     */
    function setDebug(_debug) {
      debug = !!_debug;
    }

    function Redirect($rootScope, $state, $q, $timeout, $injector) {
      var callbackQueue = [],
          redirectQueue = [],
          redirectAccepted = false,
          callIndex = 0,
          cache = {},
          redirectScope = {
            add: add,
            _go: go,
            set: set,
            get: get
          };

      $rootScope.$on('$stateChangeStart', stateChangeStart);

      return redirectScope;

      /**
       * Approve / Deny the ui-router's change
       */
      function stateChangeStart(event, toState, toParams, fromState, fromParams, options) {
        //Initiate checks
        if(!redirectAccepted) {
          event.preventDefault();
          $rootScope.$broadcast('$redirectStart', toState, toParams);
          if(options) {
            delete options.$retry;
            delete options.relative;
          }
          go({
            name: toState.name,
            params: toParams,
            //Fallback for older versions
            options: options || {}
          });
        }
      }

      /**
       * Define new redirect handler
       * @params callback {function}
       * Redirect handler. First param is the targeted route.
       * Possible options to return:
       *
       * Basic options:
       * false {boolean}: Deny the change
       * true {boolean}: Approve the change
       * route {object}: Redirect to this route
       *
       * promise {object}:
       *    Resolve with a basic option
       *    Reject: Deny the change
       */
      function add() {
        var callback = angular.noop,
            condition = '';

        if(arguments.length === 1) {
          callback = arguments[0];
        } else if(arguments.length === 2) {
          condition = arguments[0];
          callback = arguments[1];
        }

        if(!angular.isString(condition)) {
            throw new Error('Condition should be string');
        }

        if(!angular.isFunction(callback)) {
            throw new Error('Callback should be function');
        }

        callbackQueue.push({
          condition: condition,
          callback: callback
        });

        return this;
      }

      /**
       * Initiate the redirection of the specified route
       * @params {object} route
       * name
       * params
       * options
       */
      function go(route) {
        var deferred = $q.defer();
        //Doesn't exist in the redirect chain
        if(!isProcessed(route)) {
          redirectQueue = [];
          var result = checkRedirectQueue(route, ++callIndex);
          if(!result) {
            deferred.reject();
          } else if(result.then) {
            return result;
          } else {
            deferred.resolve(result);
          }
        } else {
          deferred.resolve(true);
        }
        return deferred.promise;
      }

      /**
       * Cache setter
       * @params {string} key
       * @params {mixed} value
       */
      function set(key, value) {
        cache[key] = value;
        return cache[key];
      }

      /**
       * Cache getter
       * @params {string} key
       */
      function get(key) {
        return cache[key];
      }

      /**
       * Check the targeted route in the redirect chain
       * @params {object} route
       */
      function isProcessed(route) {
        var i = 0,
            redirectQueueLength = redirectQueue.length;

        for(; i < redirectQueueLength; i++) {
          if(angular.equals(route, redirectQueue[i])) {
            return true;
          }
        }

        return false;
      }

      /**
       * New item into the redirect chain
       * @params {object} route
       */
      function addToRedirectQueue(route) {
        var i = 0,
            redirectQueueLength = redirectQueue.length,
            _route = angular.copy(route);

        if(debug) {
          console.info('Redirect to:', _route);
        }

        for(; i < redirectQueueLength; i++) {
          if(angular.equals(_route, redirectQueue[i])) {
            console.info('Redirect queue:', redirectQueue);
            throw new Error('Infinite redirect loop');
          }
        }

        redirectQueue.push(_route);
      }

      /**
       * Check every redirect function and resolve them
       * @params {object} route
       * @params {object} _callIndex
       * Private callcounter
       */
      function checkRedirectQueue(route, _callIndex) {
        var i = 0,
            result = true,
            check = function(route, ignoreRedirectQueue) {

              route.params = route.params || {};
              route.options = route.options || {};

              //New redirect was triggered - cancel the current one
              if(callIndex !== _callIndex) {
                return false;
              }

              //Approved and every callback was checked
              if(i === callbackQueue.length) {
                if(route && route.name) {
                  _go(route);
                }
                return route;
              }

              if(!ignoreRedirectQueue) {
                addToRedirectQueue(route);
              }

              var condition = callbackQueue[i].condition,
                  callback = callbackQueue[i].callback;

              if(
                !condition ||
                new RegExp('^' + condition + '$').test(route.name)
              ) {
                result = callback.call(redirectScope, route);
              } else {
                //Approved - next callback
                result = true;
              }

              return handle(route, result);
            },
            handle = function(route, result) {
              //Promise
              if(result && result.then) {
                var deferred = $q.defer();
                result.then(function(_result) {
                  deferred.resolve(handle(route, _result));
                }, function(_result) {
                  $timeout(function() {
                    otherwiseCallback($injector);
                  });
                  deferred.reject();
                });
                return deferred.promise;
              //Redirect
              } else if(result && result.name) {
                return checkRedirectQueue(result, _callIndex);
              //Approved - next callback
              } else if(result === true) {
                i++;
                return check(route, true);
              //Don't go
              } else if(!result) {
                reset();
                $timeout(function() {
                  otherwiseCallback($injector);
                });
                return false;
              //Wrong format
              } else {
                throw new Error('Wrong redirect format');
              }
            };

        return check(route);
      }

      /**
       * Reset the service
       */
      function reset() {
        redirectQueue = [];
        callIndex = 0;
      }

      /**
       * Enable and redirect
       * @param {object} route
       */
      function _go(route) {
        var stateFound = $state.get(route.name);

        reset();

        if(!stateFound) {
          $rootScope.$broadcast('$redirectNotFound', route);
        }
        if(!stateFound && notFoundCallback !== angular.noop) {
          notFoundCallback($injector);
        } else {
          redirectAccepted = true;
          $state.go(route.name, route.params, route.options);
          redirectAccepted = false;
        }
      }

    }

  }

})();
<|endoftext|>


<commit_before>'use strict'

/**
 * @module serverless-plugin-optimize
 *
 * @see {@link https://serverless.com/framework/docs/providers/aws/guide/plugins/}
 *
 * @requires 'babelify'
 * @requires 'browserify'
 * @requires 'bluebird'
 * @requires 'fs-extra'
 * @requires 'path'
 * @requires 'uglifyify'
 * */
const babelify = require('babelify')
const browserify = require('browserify')
const BbPromise = require('bluebird')
const fs = BbPromise.promisifyAll(require('fs-extra'))
const path = require('path')
const uglify = require('uglifyify')

/**
 * @classdesc Bundle, transpile to ES5 and minify your Serverless functions
 * @class Optimize
 * */
class Optimize {
  /**
   * @description Serverless Optimize
   * @constructor
   *
   * @param {!Object} serverless - Serverless object
   * @param {!Object} options - Serverless options
   * */
  constructor (serverless, options) {
    /** Serverless variables */
    this.serverless = serverless
    this.options = options
    this.custom = this.serverless.service.custom

    /** Optimize variables with default options */
    this.optimize = {
      functions: [],
      options: {
        debug: false,
        exclude: ['aws-sdk'],
        extensions: [],
        global: true,
        ignore: [],
        minify: true,
        prefix: '_optimize',
        presets: ['es2015']
      }
    }

    /** Set custom options */
    if (this.custom && this.custom.optimize) {
      /** Debug flag */
      if (this.custom.optimize.debug === true) {
        this.optimize.options.debug = this.custom.optimize.debug
      }

      /** Exclude */
      if (Array.isArray(this.custom.optimize.exclude)) {
        this.optimize.options.exclude = this.custom.optimize.exclude
      }

      /** Extensions */
      if (Array.isArray(this.custom.optimize.extensions)) {
        this.optimize.options.extensions = this.custom.optimize.extensions
      }

      /** Global transforms */
      if (this.custom.optimize.global === false) {
        this.optimize.options.global = this.custom.optimize.global
      }

      /** Ignore */
      if (Array.isArray(this.custom.optimize.ignore)) {
        this.optimize.options.ignore = this.custom.optimize.ignore
      }

      /** Minify flag */
      if (this.custom.optimize.minify === false) {
        this.optimize.options.minify = this.custom.optimize.minify
      }

      /** Optimize prefix */
      if (typeof this.custom.optimize.prefix === 'string') {
        this.optimize.options.prefix = this.custom.optimize.prefix
      }

      /** Babel presets */
      if (Array.isArray(this.custom.optimize.presets)) {
        this.optimize.options.presets = this.custom.optimize.presets
      }
    }

    /** Serverless hooks */
    this.hooks = {
      'after:deploy:function:initialize': this.afterCreateDeploymentArtifacts.bind(this),
      'before:deploy:function:initialize': this.beforeCreateDeploymentArtifacts.bind(this),
      'after:deploy:createDeploymentArtifacts': this.afterCreateDeploymentArtifacts.bind(this),
      'before:deploy:createDeploymentArtifacts': this.beforeCreateDeploymentArtifacts.bind(this)
    }
  }

  /**
   * @description Before create deployment artifacts
   *
   * @fulfil {} — Functions optimized
   * @reject {Error} Optimization error
   *
   * @return {Promise}
   * */
  beforeCreateDeploymentArtifacts () {
    /** Log optimize start */
    this.serverless.cli.log('Optimize: starting engines')

    /** Get prefix path */
    this.path = this.getPath(this.optimize.options.prefix)

    /** Clean prefix folder */
    return this.cleanFolder().then(() => {
      /** Optimize one function */
      if (this.options.function) {
        return this.optimizeFunction(this.options.function)
      } else {
        /** Optimize all functions */
        return this.optimizeAllFunctions()
      }
    })
  }

  /**
   * @description After create deployment artifacts
   *
   * @fulfil {} — Optimization finished
   * @reject {Error} Optimization error
   *
   * @return {Promise}
   * */
  afterCreateDeploymentArtifacts () {
    /** Log optimize object if debug flag is set */
    if (this.optimize.options.debug) {
      this.serverless.cli.log('Optimize: debug ' + JSON.stringify(this.optimize, null, 2))
    } else {
      /** Clean prefix folder */
      return this.cleanFolder()
    }
  }

  /**
   * @description After create deployment artifacts
   *
   * @param {string} file — File path
   *
   * @return {String} Absolute file path
   * */
  getPath (file) {
    return path.join(this.serverless.config.servicePath, file)
  }

  /**
   * @description Create prefix folder
   *
   * @fulfil {} — Folder created
   * @reject {Error} File system error
   *
   * @return {Promise}
   * */
  createFolder () {
    return fs.mkdirsAsync(this.path)
  }

  /**
   * @description Clean prefix folder
   *
   * @fulfil {} — Folder cleaned
   * @reject {Error} File system error
   *
   * @return {Promise}
   * */
  cleanFolder () {
    return fs.removeAsync(this.path)
  }

  /**
   * @description Optimize all functions
   *
   * @fulfil {} — All functions optimized
   * @reject {Error} Optimization error
   *
   * @return {Promise}
   * */
  optimizeAllFunctions () {
    /** Get functions */
    const allFunctions = this.serverless.service.getAllFunctions()

    /** Create prefix folder */
    return this.createFolder().then(() => {
      /** Optimize each function */
      return BbPromise.map(allFunctions, (functionName) => {
        return this.optimizeFunction(functionName)
      })
    })
  }

  /**
   * @description Optimize function
   *
   * @param {string} functionName - Function name
   *
   * @fulfil {} — Function optimized
   * @reject {Error} Optimization error
   *
   * @return {Promise}
   * */
  optimizeFunction (functionName) {
    /** Function object variables */
    const functionObject = this.serverless.service.getFunction(functionName)
    functionObject.package = functionObject.package || {}
    const functionFileIndex = functionObject.handler.lastIndexOf('.')
    const functionFile = this.getPath(functionObject.handler.substring(0, functionFileIndex) + '.js')
    const functionOptimize = this.optimize.options.prefix + '/' + functionObject.name
    const functionBundle = functionOptimize + '.js'

    /** Skip function */
    if (functionObject.optimize === false) {
      return BbPromise.resolve('optimization skipped')
    }

    /** Log function to optimize */
    this.serverless.cli.log('Optimize: ' + functionObject.name)

    /** Optimize object */
    let optimize = {
      bundle: functionBundle,
      handlerOriginal: functionObject.handler,
      handlerOptimize: functionOptimize + functionObject.handler.substring(functionFileIndex),
      package: {
        exclude: ['!!**', '!' + functionBundle]
      }
    }

    /** Function optimize options */
    let functionExclude = this.optimize.options.exclude
    let functionExtensions = this.optimize.options.extensions
    let functionGlobal = this.optimize.options.global
    let functionIgnore = this.optimize.options.ignore
    let functionMinify = this.optimize.options.minify
    let functionPresets = this.optimize.options.presets
    if (functionObject.optimize) {
      /** Exclude */
      if (Array.isArray(functionObject.optimize.exclude)) {
        functionExclude = optimize.exclude = functionObject.optimize.exclude
      }

      /** Extensions */
      if (Array.isArray(functionObject.optimize.extensions)) {
        functionExtensions = optimize.extensions = functionObject.optimize.extensions
      }

      /** Global transforms */
      if (typeof functionObject.optimize.global === 'boolean') {
        functionGlobal = optimize.global = functionObject.optimize.global
      }

      /** Ignore */
      if (Array.isArray(functionObject.optimize.ignore)) {
        functionIgnore = optimize.ignore = functionObject.optimize.ignore
      }

      /** Minify flag */
      if (typeof functionObject.optimize.minify === 'boolean') {
        functionMinify = optimize.minify = functionObject.optimize.minify
      }

      /** Babel presets */
      if (Array.isArray(functionObject.optimize.presets)) {
        functionPresets = optimize.presets = functionObject.optimize.presets
      }
    }

    /** Browserify */
    const bundler = browserify({
      entries: [functionFile],
      extensions: functionExtensions,
      standalone: 'handler',
      browserField: false,
      builtins: false,
      commondir: false,
      ignoreMissing: true,
      detectGlobals: true,
      insertGlobalVars: { // https://github.com/substack/node-browserify/issues/1472
        process: undefined,
        global: undefined,
        'Buffer.isBuffer': undefined,
        Buffer: undefined
      }
    })

    /** Browserify exclude */
    functionExclude.forEach((exclusion) => {
      bundler.exclude(exclusion)
    })

    /** Browserify babelify transform */
    bundler.transform(babelify, {
      global: functionGlobal,
      ignore: functionIgnore,
      presets: functionPresets
    })

    /** Browserify minify transform */
    if (functionMinify) {
      bundler.transform(uglify, {
        global: functionGlobal,
        ignore: functionIgnore
      })
    }

    /** Generate bundle */
    return new Promise((resolve, reject) => {
      bundler.bundle((error, buff) => {
        /** Bundle error */
        if (error) {
          return reject(error)
        }

        /** Write bundle */
        resolve(fs.outputFileAsync(functionBundle, buff.toString()))
      })
    }).then(() => {
      /** Add optimized function to functions array */
      this.optimize.functions.push(optimize)

      /** Update function object with optimize bundle */
      functionObject.handler = optimize.handlerOptimize

      /** Update package */
      functionObject.package = optimize.package
    })
  }
}

/** Export optimize class */
module.exports = Optimize
<commit_msg>refactor(sls): Change function hooks Thanks to @jeremydaly. Closes #23.<commit_after>'use strict'

/**
 * @module serverless-plugin-optimize
 *
 * @see {@link https://serverless.com/framework/docs/providers/aws/guide/plugins/}
 *
 * @requires 'babelify'
 * @requires 'browserify'
 * @requires 'bluebird'
 * @requires 'fs-extra'
 * @requires 'path'
 * @requires 'uglifyify'
 * */
const babelify = require('babelify')
const browserify = require('browserify')
const BbPromise = require('bluebird')
const fs = BbPromise.promisifyAll(require('fs-extra'))
const path = require('path')
const uglify = require('uglifyify')

/**
 * @classdesc Bundle, transpile to ES5 and minify your Serverless functions
 * @class Optimize
 * */
class Optimize {
  /**
   * @description Serverless Optimize
   * @constructor
   *
   * @param {!Object} serverless - Serverless object
   * @param {!Object} options - Serverless options
   * */
  constructor (serverless, options) {
    /** Serverless variables */
    this.serverless = serverless
    this.options = options
    this.custom = this.serverless.service.custom

    /** Optimize variables with default options */
    this.optimize = {
      functions: [],
      options: {
        debug: false,
        exclude: ['aws-sdk'],
        extensions: [],
        global: true,
        ignore: [],
        minify: true,
        prefix: '_optimize',
        presets: ['es2015']
      }
    }

    /** Set custom options */
    if (this.custom && this.custom.optimize) {
      /** Debug flag */
      if (this.custom.optimize.debug === true) {
        this.optimize.options.debug = this.custom.optimize.debug
      }

      /** Exclude */
      if (Array.isArray(this.custom.optimize.exclude)) {
        this.optimize.options.exclude = this.custom.optimize.exclude
      }

      /** Extensions */
      if (Array.isArray(this.custom.optimize.extensions)) {
        this.optimize.options.extensions = this.custom.optimize.extensions
      }

      /** Global transforms */
      if (this.custom.optimize.global === false) {
        this.optimize.options.global = this.custom.optimize.global
      }

      /** Ignore */
      if (Array.isArray(this.custom.optimize.ignore)) {
        this.optimize.options.ignore = this.custom.optimize.ignore
      }

      /** Minify flag */
      if (this.custom.optimize.minify === false) {
        this.optimize.options.minify = this.custom.optimize.minify
      }

      /** Optimize prefix */
      if (typeof this.custom.optimize.prefix === 'string') {
        this.optimize.options.prefix = this.custom.optimize.prefix
      }

      /** Babel presets */
      if (Array.isArray(this.custom.optimize.presets)) {
        this.optimize.options.presets = this.custom.optimize.presets
      }
    }

    /** Serverless hooks */
    this.hooks = {
      'after:deploy:function:packageFunction': this.afterCreateDeploymentArtifacts.bind(this),
      'before:deploy:function:packageFunction': this.beforeCreateDeploymentArtifacts.bind(this),
      'after:deploy:createDeploymentArtifacts': this.afterCreateDeploymentArtifacts.bind(this),
      'before:deploy:createDeploymentArtifacts': this.beforeCreateDeploymentArtifacts.bind(this)
    }
  }

  /**
   * @description Before create deployment artifacts
   *
   * @fulfil {} — Functions optimized
   * @reject {Error} Optimization error
   *
   * @return {Promise}
   * */
  beforeCreateDeploymentArtifacts () {
    /** Log optimize start */
    this.serverless.cli.log('Optimize: starting engines')

    /** Get prefix path */
    this.path = this.getPath(this.optimize.options.prefix)

    /** Clean prefix folder */
    return this.cleanFolder().then(() => {
      /** Optimize one function */
      if (this.options.function) {
        return this.optimizeFunction(this.options.function)
      } else {
        /** Optimize all functions */
        return this.optimizeAllFunctions()
      }
    })
  }

  /**
   * @description After create deployment artifacts
   *
   * @fulfil {} — Optimization finished
   * @reject {Error} Optimization error
   *
   * @return {Promise}
   * */
  afterCreateDeploymentArtifacts () {
    /** Log optimize object if debug flag is set */
    if (this.optimize.options.debug) {
      this.serverless.cli.log('Optimize: debug ' + JSON.stringify(this.optimize, null, 2))
    } else {
      /** Clean prefix folder */
      return this.cleanFolder()
    }
  }

  /**
   * @description After create deployment artifacts
   *
   * @param {string} file — File path
   *
   * @return {String} Absolute file path
   * */
  getPath (file) {
    return path.join(this.serverless.config.servicePath, file)
  }

  /**
   * @description Create prefix folder
   *
   * @fulfil {} — Folder created
   * @reject {Error} File system error
   *
   * @return {Promise}
   * */
  createFolder () {
    return fs.mkdirsAsync(this.path)
  }

  /**
   * @description Clean prefix folder
   *
   * @fulfil {} — Folder cleaned
   * @reject {Error} File system error
   *
   * @return {Promise}
   * */
  cleanFolder () {
    return fs.removeAsync(this.path)
  }

  /**
   * @description Optimize all functions
   *
   * @fulfil {} — All functions optimized
   * @reject {Error} Optimization error
   *
   * @return {Promise}
   * */
  optimizeAllFunctions () {
    /** Get functions */
    const allFunctions = this.serverless.service.getAllFunctions()

    /** Create prefix folder */
    return this.createFolder().then(() => {
      /** Optimize each function */
      return BbPromise.map(allFunctions, (functionName) => {
        return this.optimizeFunction(functionName)
      })
    })
  }

  /**
   * @description Optimize function
   *
   * @param {string} functionName - Function name
   *
   * @fulfil {} — Function optimized
   * @reject {Error} Optimization error
   *
   * @return {Promise}
   * */
  optimizeFunction (functionName) {
    /** Function object variables */
    const functionObject = this.serverless.service.getFunction(functionName)
    functionObject.package = functionObject.package || {}
    const functionFileIndex = functionObject.handler.lastIndexOf('.')
    const functionFile = this.getPath(functionObject.handler.substring(0, functionFileIndex) + '.js')
    const functionOptimize = this.optimize.options.prefix + '/' + functionObject.name
    const functionBundle = functionOptimize + '.js'

    /** Skip function */
    if (functionObject.optimize === false) {
      return BbPromise.resolve('optimization skipped')
    }

    /** Log function to optimize */
    this.serverless.cli.log('Optimize: ' + functionObject.name)

    /** Optimize object */
    let optimize = {
      bundle: functionBundle,
      handlerOriginal: functionObject.handler,
      handlerOptimize: functionOptimize + functionObject.handler.substring(functionFileIndex),
      package: {
        exclude: ['!!**', '!' + functionBundle]
      }
    }

    /** Function optimize options */
    let functionExclude = this.optimize.options.exclude
    let functionExtensions = this.optimize.options.extensions
    let functionGlobal = this.optimize.options.global
    let functionIgnore = this.optimize.options.ignore
    let functionMinify = this.optimize.options.minify
    let functionPresets = this.optimize.options.presets
    if (functionObject.optimize) {
      /** Exclude */
      if (Array.isArray(functionObject.optimize.exclude)) {
        functionExclude = optimize.exclude = functionObject.optimize.exclude
      }

      /** Extensions */
      if (Array.isArray(functionObject.optimize.extensions)) {
        functionExtensions = optimize.extensions = functionObject.optimize.extensions
      }

      /** Global transforms */
      if (typeof functionObject.optimize.global === 'boolean') {
        functionGlobal = optimize.global = functionObject.optimize.global
      }

      /** Ignore */
      if (Array.isArray(functionObject.optimize.ignore)) {
        functionIgnore = optimize.ignore = functionObject.optimize.ignore
      }

      /** Minify flag */
      if (typeof functionObject.optimize.minify === 'boolean') {
        functionMinify = optimize.minify = functionObject.optimize.minify
      }

      /** Babel presets */
      if (Array.isArray(functionObject.optimize.presets)) {
        functionPresets = optimize.presets = functionObject.optimize.presets
      }
    }

    /** Browserify */
    const bundler = browserify({
      entries: [functionFile],
      extensions: functionExtensions,
      standalone: 'handler',
      browserField: false,
      builtins: false,
      commondir: false,
      ignoreMissing: true,
      detectGlobals: true,
      insertGlobalVars: { // https://github.com/substack/node-browserify/issues/1472
        process: undefined,
        global: undefined,
        'Buffer.isBuffer': undefined,
        Buffer: undefined
      }
    })

    /** Browserify exclude */
    functionExclude.forEach((exclusion) => {
      bundler.exclude(exclusion)
    })

    /** Browserify babelify transform */
    bundler.transform(babelify, {
      global: functionGlobal,
      ignore: functionIgnore,
      presets: functionPresets
    })

    /** Browserify minify transform */
    if (functionMinify) {
      bundler.transform(uglify, {
        global: functionGlobal,
        ignore: functionIgnore
      })
    }

    /** Generate bundle */
    return new Promise((resolve, reject) => {
      bundler.bundle((error, buff) => {
        /** Bundle error */
        if (error) {
          return reject(error)
        }

        /** Write bundle */
        resolve(fs.outputFileAsync(functionBundle, buff.toString()))
      })
    }).then(() => {
      /** Add optimized function to functions array */
      this.optimize.functions.push(optimize)

      /** Update function object with optimize bundle */
      functionObject.handler = optimize.handlerOptimize

      /** Update package */
      functionObject.package = optimize.package
    })
  }
}

/** Export optimize class */
module.exports = Optimize
<|endoftext|>


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

(function() {
    'use strict';

    var spawnify    = require('../'),
        slice       = [].slice.bind(process.argv),
        argv        = slice(),
        command     = '';

        argv.shift();
        argv.shift();

        command     = argv.join(' ');

    if (command)
        spawnify(command, function(error, json) {
            console.log(json);
        });
})();
<commit_msg>feature(spawnify) stringify json before output<commit_after>#!/usr/bin/env node

(function() {
    'use strict';

    var spawnify    = require('../'),
        slice       = [].slice.bind(process.argv),
        argv        = slice(),
        command     = '';

        argv.shift();
        argv.shift();

        command     = argv.join(' ');

    if (command)
        spawnify(command, function(error, json) {
            var str = JSON.stringify(json);

            console.log(str);
        });
})();
<|endoftext|>


<commit_before>'use strict';

import AssetLoader from './assetloader';
import Input from './input';
import Loop from './loop';
import Log from './log';
import Timer from './timer';
import Math from './math';

export default {
  AssetLoader,
  Input,
  Loop,
  Log,
  Timer,
  Math
};
  <commit_msg>Add types to be exported<commit_after>'use strict';

import AssetLoader from './assetloader';
import Input from './input';
import Loop from './loop';
import Log from './log';
import Timer from './timer';
import Math from './math';
import Types from './types';

export default {
  AssetLoader,
  Input,
  Loop,
  Log,
  Timer,
  Math
};
  <|endoftext|>


<commit_before>import express, { Router } from 'express';
import mongoose from 'mongoose';
import bodyParser from 'body-parser';
import Example from './models/Example';

// Connect to port defined in environment, otherwise, default to 8080
const port = process.env.PORT || 8080;

// Create server
const app = express();

// Seed the database. Checks if an example document already exists, if not,
// it creates it in the database. Runs after the database is connected.
const seed = () => {
  Example.findOne({}, (err, example) => {
    if (err) {
      console.log(`Error seeding database: ${err}`);
    } else if (!example) {
      Example.create({ servicename: 'serviceA', message: 'Service A says hello!' }, (exampleErr, created) => {
        if (exampleErr) {
          console.log(`Error creating example document: ${exampleErr}`);
        } else {
          console.log(`Created example document: ${created}`);
        }
      });
    } else {
      console.log('Example document already created');
    }
  });
};

// Connect to the mongoDB instance running on the same machine.
const connect = () => {
  mongoose.connect('mongodb://localhost/2dv514', {
    useMongoClient: true,
  }, (err) => {
    if (err) {
      console.log(`Error connecting to database: ${err}`);
    } else {
      console.log('Connected to database');
      seed();
    }
  });
};

connect();

// Make sure that errors are reported and reconnect when disconnected.
mongoose.connection.on('error', console.error);
mongoose.connection.on('disconnected', connect);

// JSON body parser for API requests.
app.use(bodyParser.json());

const router = Router();

// The example route.
router.get('/hello', (req, res) => {
  Example.findOne({}, (err, example) => {
    if (err) return res.status(500).json({ servicename: null, message: err });
    if (!example) return res.status(404).json({ servicename: null, message: 'Not found' });

    const { servicename, message } = example;

    return res.status(200).json({ servicename, message });
  });
});

app.use(router);

// Standard 404 message when route not found.
app.use((req, res) => {
  res.status(404).json({ message: 'Not found' });
});

// Start the server!
app.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});
<commit_msg>Read DB info from env<commit_after>import express, { Router } from 'express';
import mongoose from 'mongoose';
import bodyParser from 'body-parser';
import Example from './models/Example';

// Connect to port defined in environment, otherwise, default to 8080
const port = process.env.PORT || 8080;

const dbURI = process.env.DB_URI || 'localhost';
const dbName = process.env.DB_NAME || '2dv514';

// Create server
const app = express();

// Seed the database. Checks if an example document already exists, if not,
// it creates it in the database. Runs after the database is connected.
const seed = () => {
  Example.findOne({}, (err, example) => {
    if (err) {
      console.log(`Error seeding database: ${err}`);
    } else if (!example) {
      Example.create({ servicename: 'serviceA', message: 'Service A says hello!' }, (exampleErr, created) => {
        if (exampleErr) {
          console.log(`Error creating example document: ${exampleErr}`);
        } else {
          console.log(`Created example document: ${created}`);
        }
      });
    } else {
      console.log('Example document already created');
    }
  });
};

// Connect to the mongoDB instance running on the same machine.
const connect = () => {
  mongoose.connect(`mongodb://${dbURI}/${dbName}`, {
    useMongoClient: true,
  }, (err) => {
    if (err) {
      console.log(`Error connecting to database: ${err}`);
    } else {
      console.log('Connected to database');
      seed();
    }
  });
};

connect();

// Make sure that errors are reported and reconnect when disconnected.
mongoose.connection.on('error', console.error);
mongoose.connection.on('disconnected', connect);

// JSON body parser for API requests.
app.use(bodyParser.json());

const router = Router();

// The example route.
router.get('/hello', (req, res) => {
  Example.findOne({}, (err, example) => {
    if (err) return res.status(500).json({ servicename: null, message: err });
    if (!example) return res.status(404).json({ servicename: null, message: 'Not found' });

    const { servicename, message } = example;

    return res.status(200).json({ servicename, message });
  });
});

app.use(router);

// Standard 404 message when route not found.
app.use((req, res) => {
  res.status(404).json({ message: 'Not found' });
});

// Start the server!
app.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});
<|endoftext|>


<commit_before>var interpreter = require('./interpreter');
var fromNow = require('./from-now');
var assert = require('assert');
var {
  isString, isNumber, isBool,
  isArray, isObject, isFunction,
  isTruthy,
} = require('./type-utils');
var addBuiltins = require('./builtins');
var {JSONTemplateError, TemplateError} = require('./error');

let flattenDeep = (a) => {
  return Array.isArray(a) ? [].concat(...a.map(flattenDeep)) : a;
};

let interpolate = (string, context) => {
  let result = '';
  let remaining = string;
  let offset;
  while ((offset = remaining.search(/\$?\${/g)) !== -1) {
    result += remaining.slice(0, offset);

    if (remaining[offset+1] != '$') {
      let v = interpreter.parseUntilTerminator(remaining.slice(offset), 2, '}', context);
      if (isArray(v.result) || isObject(v.result)) {
        let input = remaining.slice(offset + 2, offset + v.offset);
        throw new TemplateError(`interpolation of '${input}' produced an array or object`);
      }

      // if it is null, result should just be appended with empty string
      if (v.result === null) {
        result += '';
      } else {
        result += v.result.toString();
      }

      remaining = remaining.slice(offset + v.offset + 1);
    } else {
      result += '${';
      remaining = remaining.slice(offset + 3);
    }
  }
  result += remaining;
  return result;
};

// Object used to indicate deleteMarker
let deleteMarker = {};

let operators = {};

operators.$eval = (template, context) => {
  let value = render(template['$eval'], context);
  return interpreter.parse(value, context);
};

operators.$flatten = (template, context) => {
  let value = render(template['$flatten'], context);

  if (!isArray(value)) {
    throw new TemplateError('$flatten value must evaluate to an array');
  }

  return [].concat(...value);
};

operators.$flattenDeep = (template, context) => {
  let value = render(template['$flattenDeep'], context);

  if (!isArray(value)) {
    throw new TemplateError('$flattenDeep value must evaluate to an array');
  }

  return flattenDeep(value);
};

operators.$fromNow = (template, context) => {
  let value = render(template['$fromNow'], context);
  let reference = context.now;
  if (template['from']) {
    reference = render(template['from'], context);
  }
  if (!isString(value)) {
    throw new TemplateError('$fromNow expects a string');
  }
  return fromNow(value, reference);
};

operators.$if = (template, context) => {
  if (!isString(template['$if'])) {
    throw new TemplateError('$if can evaluate string expressions only');
  }
  if (isTruthy(interpreter.parse(template['$if'], context))) {
    return template.hasOwnProperty('then') ? render(template.then, context) : deleteMarker;
  }
  return template.hasOwnProperty('else') ? render(template.else, context) : deleteMarker;
};

operators.$json = (template, context) => {
  return JSON.stringify(render(template['$json'], context));
};

operators.$let = (template, context) => {
  let variables = render(template['$let'], context);

  var context_copy = Object.assign(context, variables);

  if (!isObject(variables)) {
    throw new TemplateError('$let value must evaluate to an object');
  } else {
    let match = /^[a-zA-Z_][a-zA-Z0-9_]*$/.exec(Object.keys(variables));
    if (!match) {
      throw new TemplateError('top level keys of $let must follow /[a-zA-Z_][a-zA-Z0-9_]*/');
    }
  }

  if (template.in == undefined) {
    throw new TemplateError('$let operator requires an `in` clause');
  }

  return render(template.in, context_copy);
};

operators.$map = (template, context) => {
  let value = render(template['$map'], context);
  if (!isArray(value) && !isObject(value)) {
    throw new TemplateError('$map value must evaluate to an array or object');
  }

  if (Object.keys(template).length !== 2) {
    throw new TemplateError('$map requires cannot have more than two properties');
  }

  let eachKey = Object.keys(template).filter(k => k !== '$map')[0];
  let match = /^each\(([a-zA-Z_][a-zA-Z0-9_]*)\)$/.exec(eachKey);
  if (!match) {
    throw new TemplateError('$map requires each(identifier) syntax');
  }

  let x = match[1];
  let each = template[eachKey];

  let object = isObject(value);

  if (object) {
    value = Object.keys(value).map(key => ({key, val: value[key]}));
  }

  value = value.map(v => render(each, Object.assign({}, context, {[x]: v})))
              .filter(v => v !== deleteMarker);

  if (object) {
    return value.reduce((a, o) => Object.assign(a, o), {});
  } else {
    return value;
  }
};

operators.$merge = (template, context) => {
  let value = render(template['$merge'], context);

  if (!isArray(value) || value.some(o => !isObject(o))) {
    throw new TemplateError('$merge value must evaluate to an array of objects');
  }

  return Object.assign({}, ...value);
};

operators.$mergeDeep = (template, context) => {
  let value = render(template['$mergeDeep'], context);

  if (!isArray(value) || value.some(o => !isObject(o))) {
    throw new TemplateError('$mergeDeep value must evaluate to an array of objects');
  }

  if (value.length === 0) {
    return {};
  }
  // merge two values, preferring the right but concatenating lists and
  // recursively merging objects
  let merge = (l, r) => {
    if (isArray(l) && isArray(r)) {
      return l.concat(r);
    }
    if (isObject(l) && isObject(r)) {
      let res = Object.assign({}, l);
      for (let p in r) { // eslint-disable-line taskcluster/no-for-in
        if (p in l) {
          res[p] = merge(l[p], r[p]);
        } else {
          res[p] = r[p];
        }
      }
      return res;
    }
    return r;
  };
  // start with the first element of the list
  return value.reduce(merge, value.shift());

  return Object.assign({}, ...value);
};

operators.$reverse = (template, context) => {
  let value = render(template['$reverse'], context);

  if (!isArray(value) && !isArray(template['$reverse'])) {
    throw new TemplateError('$reverse value must evaluate to an array of objects');
  }

  if (!isArray(value)) {
    throw new TemplateError('$reverse requires array as value');
  }
  return value.reverse();
};

operators.$sort = (template, context) => {
  let value = render(template['$sort'], context);
  if (!isArray(value)) {
    throw new TemplateError('$sort requires array as value');
  }

  let byKey = Object.keys(template).filter(k => k !== '$sort')[0];
  let match = /^by\(([a-zA-Z_][a-zA-Z0-9_]*)\)$/.exec(byKey);
  let by;
  if (match) {
    let contextClone = Object.assign({}, context);
    let x = match[1];
    let byExpr = template[byKey];
    by = value => {
      contextClone[x] = value;
      return interpreter.parse(byExpr, contextClone);
    };
  } else {
    let needBy = value.some(v => isArray(v) || isObject(v));
    if (needBy) {
      throw new TemplateError('$sort requires by(identifier) for sorting arrays of objects/arrays');
    }
    by = value => value;
  }

  // tag each value with its `by` value (schwartzian tranform)
  let tagged = value.map(e => [by(e), e]);

  // check types of the `by` values
  if (tagged.length > 0) {
    let eltType = typeof tagged[0][0];
    if (eltType !== 'number' && eltType !== 'string' ||
        tagged.some(e => eltType !== typeof e[0])) {
      throw new TemplateError('$sort requires all sorted values have the same type');
    }
  }

  // finish the schwartzian transform
  return tagged
    .sort((a, b) => {
      a = a[0];
      b = b[0];
      if (a < b) { return -1; }
      if (a > b) { return 1; }
      return 0;
    })
    .map(e => e[1]);
};

let render = (template, context) => {
  if (isNumber(template) || isBool(template) || template === null) {
    return template;
  }
  if (isString(template)) {
    return interpolate(template, context);
  }
  if (isArray(template)) {
    return template.map((v, i) => {
      try {
        return render(v, context);
      } catch (err) {
        if (err instanceof JSONTemplateError) {
          err.add_location(`[${i}]`);
        }
        throw err;
      }
    }).filter((v) => v !== deleteMarker);
  }

  let matches = Object.keys(operators).filter(c => template.hasOwnProperty(c));
  if (matches.length > 1) {
    throw new TemplateError('only one operator allowed');
  }
  if (matches.length === 1) {
    return operators[matches[0]](template, context);
  }

  // clone object
  let result = {};
  for (let key of Object.keys(template)) {
    let value;
    try {
      value = render(template[key], context);
    } catch (err) {
      if (err instanceof JSONTemplateError) {
        if (/^[a-zA-Z][a-zA-Z0-9]*$/.test(key)) {
          err.add_location(`.${key}`);
        } else {
          err.add_location(`[${JSON.stringify(key)}]`);
        }
      }
      throw err;
    }
    if (value !== deleteMarker) {
      if (key.startsWith('$$') && operators.hasOwnProperty(key.substr(1))) {
        key = key.substr(1);
      }

      result[interpolate(key, context)] = value;
    }
  }
  return result;
};

module.exports = (template, context = {}) => {
  let test = Object.keys(context).every(v => /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(v));
  if (!test) {
    throw new TemplateError('top level keys of context must follow /[a-zA-Z_][a-zA-Z0-9_]*/');
  }
  context = addBuiltins(Object.assign({}, {now: new Date()}, context));
  let result = render(template, context);
  if (result === deleteMarker) {
    return null;
  }
  return result;
};
<commit_msg>remove dead code<commit_after>var interpreter = require('./interpreter');
var fromNow = require('./from-now');
var assert = require('assert');
var {
  isString, isNumber, isBool,
  isArray, isObject, isFunction,
  isTruthy,
} = require('./type-utils');
var addBuiltins = require('./builtins');
var {JSONTemplateError, TemplateError} = require('./error');

let flattenDeep = (a) => {
  return Array.isArray(a) ? [].concat(...a.map(flattenDeep)) : a;
};

let interpolate = (string, context) => {
  let result = '';
  let remaining = string;
  let offset;
  while ((offset = remaining.search(/\$?\${/g)) !== -1) {
    result += remaining.slice(0, offset);

    if (remaining[offset+1] != '$') {
      let v = interpreter.parseUntilTerminator(remaining.slice(offset), 2, '}', context);
      if (isArray(v.result) || isObject(v.result)) {
        let input = remaining.slice(offset + 2, offset + v.offset);
        throw new TemplateError(`interpolation of '${input}' produced an array or object`);
      }

      // if it is null, result should just be appended with empty string
      if (v.result === null) {
        result += '';
      } else {
        result += v.result.toString();
      }

      remaining = remaining.slice(offset + v.offset + 1);
    } else {
      result += '${';
      remaining = remaining.slice(offset + 3);
    }
  }
  result += remaining;
  return result;
};

// Object used to indicate deleteMarker
let deleteMarker = {};

let operators = {};

operators.$eval = (template, context) => {
  let value = render(template['$eval'], context);
  return interpreter.parse(value, context);
};

operators.$flatten = (template, context) => {
  let value = render(template['$flatten'], context);

  if (!isArray(value)) {
    throw new TemplateError('$flatten value must evaluate to an array');
  }

  return [].concat(...value);
};

operators.$flattenDeep = (template, context) => {
  let value = render(template['$flattenDeep'], context);

  if (!isArray(value)) {
    throw new TemplateError('$flattenDeep value must evaluate to an array');
  }

  return flattenDeep(value);
};

operators.$fromNow = (template, context) => {
  let value = render(template['$fromNow'], context);
  let reference = context.now;
  if (template['from']) {
    reference = render(template['from'], context);
  }
  if (!isString(value)) {
    throw new TemplateError('$fromNow expects a string');
  }
  return fromNow(value, reference);
};

operators.$if = (template, context) => {
  if (!isString(template['$if'])) {
    throw new TemplateError('$if can evaluate string expressions only');
  }
  if (isTruthy(interpreter.parse(template['$if'], context))) {
    return template.hasOwnProperty('then') ? render(template.then, context) : deleteMarker;
  }
  return template.hasOwnProperty('else') ? render(template.else, context) : deleteMarker;
};

operators.$json = (template, context) => {
  return JSON.stringify(render(template['$json'], context));
};

operators.$let = (template, context) => {
  let variables = render(template['$let'], context);

  var context_copy = Object.assign(context, variables);

  if (!isObject(variables)) {
    throw new TemplateError('$let value must evaluate to an object');
  } else {
    let match = /^[a-zA-Z_][a-zA-Z0-9_]*$/.exec(Object.keys(variables));
    if (!match) {
      throw new TemplateError('top level keys of $let must follow /[a-zA-Z_][a-zA-Z0-9_]*/');
    }
  }

  if (template.in == undefined) {
    throw new TemplateError('$let operator requires an `in` clause');
  }

  return render(template.in, context_copy);
};

operators.$map = (template, context) => {
  let value = render(template['$map'], context);
  if (!isArray(value) && !isObject(value)) {
    throw new TemplateError('$map value must evaluate to an array or object');
  }

  if (Object.keys(template).length !== 2) {
    throw new TemplateError('$map requires cannot have more than two properties');
  }

  let eachKey = Object.keys(template).filter(k => k !== '$map')[0];
  let match = /^each\(([a-zA-Z_][a-zA-Z0-9_]*)\)$/.exec(eachKey);
  if (!match) {
    throw new TemplateError('$map requires each(identifier) syntax');
  }

  let x = match[1];
  let each = template[eachKey];

  let object = isObject(value);

  if (object) {
    value = Object.keys(value).map(key => ({key, val: value[key]}));
  }

  value = value.map(v => render(each, Object.assign({}, context, {[x]: v})))
              .filter(v => v !== deleteMarker);

  if (object) {
    return value.reduce((a, o) => Object.assign(a, o), {});
  } else {
    return value;
  }
};

operators.$merge = (template, context) => {
  let value = render(template['$merge'], context);

  if (!isArray(value) || value.some(o => !isObject(o))) {
    throw new TemplateError('$merge value must evaluate to an array of objects');
  }

  return Object.assign({}, ...value);
};

operators.$mergeDeep = (template, context) => {
  let value = render(template['$mergeDeep'], context);

  if (!isArray(value) || value.some(o => !isObject(o))) {
    throw new TemplateError('$mergeDeep value must evaluate to an array of objects');
  }

  if (value.length === 0) {
    return {};
  }
  // merge two values, preferring the right but concatenating lists and
  // recursively merging objects
  let merge = (l, r) => {
    if (isArray(l) && isArray(r)) {
      return l.concat(r);
    }
    if (isObject(l) && isObject(r)) {
      let res = Object.assign({}, l);
      for (let p in r) { // eslint-disable-line taskcluster/no-for-in
        if (p in l) {
          res[p] = merge(l[p], r[p]);
        } else {
          res[p] = r[p];
        }
      }
      return res;
    }
    return r;
  };
  // start with the first element of the list
  return value.reduce(merge, value.shift());
};

operators.$reverse = (template, context) => {
  let value = render(template['$reverse'], context);

  if (!isArray(value) && !isArray(template['$reverse'])) {
    throw new TemplateError('$reverse value must evaluate to an array of objects');
  }

  if (!isArray(value)) {
    throw new TemplateError('$reverse requires array as value');
  }
  return value.reverse();
};

operators.$sort = (template, context) => {
  let value = render(template['$sort'], context);
  if (!isArray(value)) {
    throw new TemplateError('$sort requires array as value');
  }

  let byKey = Object.keys(template).filter(k => k !== '$sort')[0];
  let match = /^by\(([a-zA-Z_][a-zA-Z0-9_]*)\)$/.exec(byKey);
  let by;
  if (match) {
    let contextClone = Object.assign({}, context);
    let x = match[1];
    let byExpr = template[byKey];
    by = value => {
      contextClone[x] = value;
      return interpreter.parse(byExpr, contextClone);
    };
  } else {
    let needBy = value.some(v => isArray(v) || isObject(v));
    if (needBy) {
      throw new TemplateError('$sort requires by(identifier) for sorting arrays of objects/arrays');
    }
    by = value => value;
  }

  // tag each value with its `by` value (schwartzian tranform)
  let tagged = value.map(e => [by(e), e]);

  // check types of the `by` values
  if (tagged.length > 0) {
    let eltType = typeof tagged[0][0];
    if (eltType !== 'number' && eltType !== 'string' ||
        tagged.some(e => eltType !== typeof e[0])) {
      throw new TemplateError('$sort requires all sorted values have the same type');
    }
  }

  // finish the schwartzian transform
  return tagged
    .sort((a, b) => {
      a = a[0];
      b = b[0];
      if (a < b) { return -1; }
      if (a > b) { return 1; }
      return 0;
    })
    .map(e => e[1]);
};

let render = (template, context) => {
  if (isNumber(template) || isBool(template) || template === null) {
    return template;
  }
  if (isString(template)) {
    return interpolate(template, context);
  }
  if (isArray(template)) {
    return template.map((v, i) => {
      try {
        return render(v, context);
      } catch (err) {
        if (err instanceof JSONTemplateError) {
          err.add_location(`[${i}]`);
        }
        throw err;
      }
    }).filter((v) => v !== deleteMarker);
  }

  let matches = Object.keys(operators).filter(c => template.hasOwnProperty(c));
  if (matches.length > 1) {
    throw new TemplateError('only one operator allowed');
  }
  if (matches.length === 1) {
    return operators[matches[0]](template, context);
  }

  // clone object
  let result = {};
  for (let key of Object.keys(template)) {
    let value;
    try {
      value = render(template[key], context);
    } catch (err) {
      if (err instanceof JSONTemplateError) {
        if (/^[a-zA-Z][a-zA-Z0-9]*$/.test(key)) {
          err.add_location(`.${key}`);
        } else {
          err.add_location(`[${JSON.stringify(key)}]`);
        }
      }
      throw err;
    }
    if (value !== deleteMarker) {
      if (key.startsWith('$$') && operators.hasOwnProperty(key.substr(1))) {
        key = key.substr(1);
      }

      result[interpolate(key, context)] = value;
    }
  }
  return result;
};

module.exports = (template, context = {}) => {
  let test = Object.keys(context).every(v => /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(v));
  if (!test) {
    throw new TemplateError('top level keys of context must follow /[a-zA-Z_][a-zA-Z0-9_]*/');
  }
  context = addBuiltins(Object.assign({}, {now: new Date()}, context));
  let result = render(template, context);
  if (result === deleteMarker) {
    return null;
  }
  return result;
};
<|endoftext|>