<commit_before>import { call, put } from 'redux-saga/effects'
import { takeEvery, takeLatest } from 'redux-saga'

// import { browserHistory } from 'react-router'

import Product from './version'
import { logDebug, hidePassword, fileDownload } from './helpers'

import {
  getAllVms,
  loginSuccessful,
  loginFailed,
  failedExternalAction,
  loadInProgress,
  setVmDetailToShow,
  updateIcons,
  setVmDisks,
  updateVms,
  removeVms,
  vmActionInProgress,
  setVmConsoles,
  removeMissingVms,
  persistState,
  getSingleVm,
  setOvirtApiVersion,
} from './actions'

import {
  LOGIN,
  LOGOUT,
  GET_ALL_VMS,
  PERSIST_STATE,
  SHUTDOWN_VM,
  RESTART_VM,
  START_VM,
  GET_CONSOLE_VM,
  SUSPEND_VM,
  SELECT_VM_DETAIL,
  SCHEDULER__1_MIN,
} from './constants'

// import store from './store'
import Api from './ovirtapi'
import { persistStateToLocalStorage } from './storage'
import Selectors from './selectors'
import AppConfiguration from './config'

function * foreach (array, fn, context) {
  var i = 0
  var length = array.length

  for (;i < length; i++) {
    yield * fn.call(context, array[i], i, array)
  }
}

const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms))

// TODO: following generators should be better part of the Api -- Revise

function* callExternalAction (methodName, method, action, canBeMissing = false) {
  try {
    logDebug(`External action ${methodName}() starts on ${JSON.stringify(hidePassword({ action }))}`)
    const result = yield call(method, action.payload)
    return result
  } catch (e) {
    if (!canBeMissing || e.status !== 404) {
      logDebug(`External action exception: ${JSON.stringify(e)}`)
      yield put(failedExternalAction({ exception: e, shortMessage: shortErrorMessage({ action }), action }))
    }
    return { error: e }
  }
}

function* persistStateSaga () {
  yield persistStateToLocalStorage({ icons: Selectors.getAllIcons().toJS() })
}

// TODO: implement 'renew the token'
function* login (action) {
  yield put(loadInProgress({ value: true }))

  let token
  let result = {}
  if (action.payload.token) {
    token = action.payload.token
  } else { // recently not used since SSO, TODO: remove
    result = yield callExternalAction('login', Api.login, action)
    if (result && result['access_token']) {
      token = result['access_token']
    }
  }

  if (token) {
    const username = action.payload.credentials.username
    // persistTokenToSessionStorage({ token, username })
    yield put(loginSuccessful({ token, username }))

    const oVirtMeta = yield callExternalAction('getOvirtApiMeta', Api.getOvirtApiMeta, action)
    if (yield checkOvirtApiVersion(oVirtMeta)) {
      yield put(getAllVms({ shallowFetch: false }))
    } else { // oVirt API of incompatible version
      console.error('oVirt api version check failed')
      yield put(failedExternalAction({
        message: composeIncompatibleOVirtApiVersionMessage(oVirtMeta),
        shortMessage: 'oVirt API version check failed' }))
      yield put(yield put(loadInProgress({ value: false })))
    }
  } else {
    yield put(loginFailed({
      errorCode: result['error_code'] ? result['error_code'] : 'no_access',
      message: result['error'] ? (result.error['statusText'] ? result.error['statusText'] : JSON.stringify(result['error'])) : 'Login Failed',
    }))
    yield put(yield put(loadInProgress({ value: false })))
  }
}

function composeIncompatibleOVirtApiVersionMessage (oVirtMeta) {
  const requested = `${Product.ovirtApiVersionRequired.major}.${Product.ovirtApiVersionRequired.minor}`
  let found
  if (!(oVirtMeta && oVirtMeta['product_info'] && oVirtMeta['product_info']['version'] &&
    oVirtMeta['product_info']['version']['major'] && oVirtMeta['product_info']['version']['minor'])) {
    found = JSON.stringify(oVirtMeta)
  } else {
    const version = oVirtMeta['product_info']['version']
    found = `${version.major}.${version.minor}`
  }
  return `oVirt API version requested >= ${requested}, but ${found} found`
}

function compareVersion (actual, required) {
  logDebug(`compareVersion(), actual=${JSON.stringify(actual)}, required=${JSON.stringify(required)}`)

  // assuming backward compatibility of oVirt API
  if (actual.major >= required.major) {
    if (actual.major === required.major) {
      if (actual.minor < required.minor) {
        return false
      }
    }
    return true
  }
  return false
}

function* checkOvirtApiVersion (oVirtMeta) {
  if (!(oVirtMeta && oVirtMeta['product_info'] && oVirtMeta['product_info']['version'] &&
    oVirtMeta['product_info']['version']['major'] && oVirtMeta['product_info']['version']['minor'])) {
    console.error('Incompatible oVirt API version: ', oVirtMeta)
    yield put(setOvirtApiVersion({ passed: false, ...oVirtMeta }))
    return false
  }

  const actual = oVirtMeta['product_info']['version']

  const required = Product.ovirtApiVersionRequired
  const passed = compareVersion({ major: parseInt(actual.major), minor: parseInt(actual.minor) }, required)

  yield put(setOvirtApiVersion({ passed, ...actual }))

  return passed
}

/*
function* onLoginSuccessful () {
  const redirectUrl = store.getState().router
  browserHistory.replace(redirectUrl)
}
*/

function* logout () {
  window.location.href = `${AppConfiguration.applicationURL}/sso/logout`
//  clearTokenFromSessionStorage()
//  browserHistory.replace('/login')
}

function* fetchUnknwonIconsForVms ({ vms }) {
  // unique iconIds from all vms
  const vmIconIds = new Set(vms.map(vm => vm.icons.small.id))
  vms.map(vm => vm.icons.large.id).forEach(id => vmIconIds.add(id))

  // reduce to just unknown
  const allKnownIcons = Selectors.getAllIcons()
  const iconIds = [...vmIconIds].filter(id => !allKnownIcons.get(id))

  yield * foreach(iconIds, function* (iconId) {
    yield fetchIcon({ iconId })
  })
}

function* fetchIcon ({ iconId }) {
  if (iconId) {
    const icon = yield callExternalAction('icon', Api.icon, { payload: { id: iconId } })
    if (icon['media_type'] && icon['data']) {
      yield put(updateIcons({ icons: [Api.iconToInternal({ icon })] }))
    }
  }
}

function* fetchAllVms (action) {
  const { shallowFetch } = action.payload

  // TODO: paging: split this call to a loop per up to 25 vms
  const allVms = yield callExternalAction('getAllVms', Api.getAllVms, action)

  if (allVms && allVms['vm']) { // array
    const internalVms = allVms.vm.map(vm => Api.vmToInternal({ vm }))

    const vmIdsToPreserve = internalVms.map(vm => vm.id)
    yield put(removeMissingVms({ vmIdsToPreserve }))

    yield put(updateVms({ vms: internalVms, copySubResources: true }))

    // TODO: is removing of icons needed? I.e. when icon is removed or changed on the server
    yield fetchUnknwonIconsForVms({ vms: internalVms })

    if (!shallowFetch) {
      yield fetchConsoleMetadatas({ vms: internalVms })
      yield fetchDisks({ vms: internalVms })
    } else {
      logDebug('fetchAllVms() shallow fetch requested - skipping other resources')
    }
  }

  yield put(loadInProgress({ value: false }))
  yield put(persistState())
}

function* fetchDisks ({ vms }) {
  yield * foreach(vms, function* (vm) {
    const vmId = vm.id
    const disks = yield fetchVmDisks({ vmId })
    if (disks && disks.length > 0) {
      yield put(setVmDisks({ vmId, disks }))
    }
  })
}

function* fetchConsoleMetadatas ({ vms }) {
  yield * foreach(vms, function* (vm) {
    const consolesInternal = yield fetchConsoleVmMeta({ vmId: vm.id })
    yield put(setVmConsoles({ vmId: vm.id, consoles: consolesInternal }))
  })
}

function* fetchSingleVm (action) {
  const vm = yield callExternalAction('getVm', Api.getVm, action, true)

  if (vm && vm.id) {
    const internalVm = Api.vmToInternal({ vm })

    internalVm.disks = yield fetchVmDisks({ vmId: internalVm.id })
    internalVm.consoles = yield fetchConsoleVmMeta({ vmId: internalVm.id })

    yield put(updateVms({ vms: [internalVm] }))
  } else {
    if (vm && vm.error && vm.error.status === 404) {
      yield put(removeVms({ vmIds: [action.payload.vmId] }))
    }
  }
}

function* fetchVmDisks ({ vmId }) {
  const diskattachments = yield callExternalAction('diskattachments', Api.diskattachments, { payload: { vmId } })

  if (diskattachments && diskattachments['disk_attachment']) { // array
    const internalDisks = []
    yield * foreach(diskattachments['disk_attachment'], function* (attachment) {
      const diskId = attachment.disk.id
      const disk = yield callExternalAction('disk', Api.disk, { payload: { diskId } })
      internalDisks.push(Api.diskToInternal({ disk, attachment }))
    })
    return internalDisks
  }
  return []
}

function* startProgress ({ vmId, name }) {
  yield put(vmActionInProgress({ vmId, name, started: true }))
}

function* stopProgress ({ vmId, name, result }) {
  if (result && result.status === 'complete') {
      // do not call 'end of in progress' if successful,
      // since UI will be updated by refresh
    yield delay(5 * 1000)
    yield fetchSingleVm(getSingleVm({ vmId }))
    yield delay(30 * 1000)
    yield fetchSingleVm(getSingleVm({ vmId }))
  }

  yield put(vmActionInProgress({ vmId, name, started: false }))
}

function* shutdownVm (action) {
  yield startProgress({ vmId: action.payload.vmId, name: 'shutdown' })
  const result = yield callExternalAction('shutdown', Api.shutdown, action)
  yield stopProgress({ vmId: action.payload.vmId, name: 'shutdown', result })
}

function* restartVm (action) {
  yield startProgress({ vmId: action.payload.vmId, name: 'restart' })
  const result = yield callExternalAction('restart', Api.restart, action)
  yield stopProgress({ vmId: action.payload.vmId, name: 'restart', result })
}

function* suspendVm (action) {
  yield startProgress({ vmId: action.payload.vmId, name: 'suspend' })
  const result = yield callExternalAction('suspend', Api.suspend, action)
  yield stopProgress({ vmId: action.payload.vmId, name: 'suspend', result })
}

function* startVm (action) {
  yield startProgress({ vmId: action.payload.vmId, name: 'start' })
  const result = yield callExternalAction('start', Api.start, action)
  // TODO: check status at refresh --> conditional refresh wait_for_launch
  yield stopProgress({ vmId: action.payload.vmId, name: 'start', result })
}

function* fetchConsoleVmMeta ({ vmId }) {
  const consoles = yield callExternalAction('consoles', Api.consoles, { action: 'INTERNAL_CONSOLES', payload: { vmId } })

  if (consoles && consoles['graphics_console']) { // && consoles['graphics_console'].length > 0) {
    return Api.consolesToInternal({ consoles })
  }
  return []
}

function* getConsoleVm (action) {
  let { vmId, consoleId } = action.payload

  if (!consoleId) {
    yield put(vmActionInProgress({ vmId, name: 'getConsole', started: true }))
    const consolesInternal = yield fetchConsoleVmMeta({ vmId }) // refresh metadata
    yield put(setVmConsoles({ vmId, consoles: consolesInternal }))
    yield put(vmActionInProgress({ vmId, name: 'getConsole', started: false }))

    // TODO: choose user default over just 'SPICE'
    if (consolesInternal && consolesInternal.length > 0) {
      let console = consolesInternal.find(c => c.protocol === 'spice') || consolesInternal[0]
      consoleId = console.id
    }
  }

  if (consoleId) {
    const data = yield callExternalAction('console', Api.console, { action: 'INTERNAL_CONSOLE', payload: { vmId, consoleId } })
    if (data.error === undefined) {
      fileDownload({ data, fileName: 'console.vv', mimeType: 'application/x-virt-viewer' })
    }
  }
}

function* selectVmDetail (action) {
  yield put(setVmDetailToShow({ vmId: action.payload.vmId }))
  yield fetchSingleVm(getSingleVm({ vmId: action.payload.vmId }))
}

function* schedulerPerMinute (action) {
  logDebug('Starting schedulerPerMinute() scheduler')

  // TODO: do we need to stop the loop? Consider takeLatest in the rootSaga 'restarts' the loop if needed
  while (true) {
    yield delay(60 * 1000) // 1 minute
    logDebug('schedulerPerMinute() event')

    const oVirtVersion = Selectors.getOvirtVersion()
    if (oVirtVersion.passed) {
      // Actions to be executed no more than once per minute:
      // TODO: allow user to enable/disable the autorefresh
      yield put(getAllVms({ shallowFetch: true }))
    } else {
      logDebug('schedulerPerMinute() event skipped since oVirt API version does not match')
    }
  }
}

export function *rootSaga () {
  yield [
    takeEvery(LOGIN, login),
    // takeEvery('LOGIN_SUCCESSFUL', onLoginSuccessful),
    takeEvery(LOGOUT, logout),
    takeLatest(GET_ALL_VMS, fetchAllVms),
    takeLatest(PERSIST_STATE, persistStateSaga),

    takeEvery(SHUTDOWN_VM, shutdownVm),
    takeEvery(RESTART_VM, restartVm),
    takeEvery(START_VM, startVm),
    takeEvery(GET_CONSOLE_VM, getConsoleVm),
    takeEvery(SUSPEND_VM, suspendVm),

    takeEvery(SELECT_VM_DETAIL, selectVmDetail),

    takeLatest(SCHEDULER__1_MIN, schedulerPerMinute),
  ]
}

// TODO: translate
// TODO: move to ovirt-ui-actions
const shortMessages = {
  'START_VM': 'Failed to start the VM',
  'RESTART_VM': 'Failed to restart the VM',
  'SHUTDOWN_VM': 'Failed to shutdown the VM',
  'GET_CONSOLE_VM': 'Failed to get the VM console',
  'SUSPEND_VM': 'Failed to suspend the VM',
}

function shortErrorMessage ({ action }) {
  return shortMessages[action.type] ? shortMessages[action.type] : `${action.type} failed` // TODO: translate
}
<commit_msg>Fix error response when mixing content<commit_after>import { call, put } from 'redux-saga/effects'
import { takeEvery, takeLatest } from 'redux-saga'

// import { browserHistory } from 'react-router'

import Product from './version'
import { logDebug, hidePassword, fileDownload } from './helpers'

import {
  getAllVms,
  loginSuccessful,
  loginFailed,
  failedExternalAction,
  loadInProgress,
  setVmDetailToShow,
  updateIcons,
  setVmDisks,
  updateVms,
  removeVms,
  vmActionInProgress,
  setVmConsoles,
  removeMissingVms,
  persistState,
  getSingleVm,
  setOvirtApiVersion,
} from './actions'

import {
  LOGIN,
  LOGOUT,
  GET_ALL_VMS,
  PERSIST_STATE,
  SHUTDOWN_VM,
  RESTART_VM,
  START_VM,
  GET_CONSOLE_VM,
  SUSPEND_VM,
  SELECT_VM_DETAIL,
  SCHEDULER__1_MIN,
} from './constants'

// import store from './store'
import Api from './ovirtapi'
import { persistStateToLocalStorage } from './storage'
import Selectors from './selectors'
import AppConfiguration from './config'

function * foreach (array, fn, context) {
  var i = 0
  var length = array.length

  for (;i < length; i++) {
    yield * fn.call(context, array[i], i, array)
  }
}

const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms))

// TODO: following generators should be better part of the Api -- Revise

function* callExternalAction (methodName, method, action, canBeMissing = false) {
  try {
    logDebug(`External action ${methodName}() starts on ${JSON.stringify(hidePassword({ action }))}`)
    const result = yield call(method, action.payload)
    return result
  } catch (e) {
    if (!canBeMissing || e.status !== 404) {
      logDebug(`External action exception: ${JSON.stringify(e)}`)

      let shortMessage = shortErrorMessage({ action })
      if (e.status === 0 && e.statusText === 'error') { // special case, mixing https and http
        shortMessage = 'oVirt API connection failed'
        e.statusText = 'Unable to connect to oVirt REST API. Please check URL and protocol (https).'
      }

      yield put(failedExternalAction({
        exception: e,
        shortMessage,
        action,
      }))
    }
    return { error: e }
  }
}

function* persistStateSaga () {
  yield persistStateToLocalStorage({ icons: Selectors.getAllIcons().toJS() })
}

// TODO: implement 'renew the token'
function* login (action) {
  yield put(loadInProgress({ value: true }))

  let token
  let result = {}
  if (action.payload.token) {
    token = action.payload.token
  } else { // recently not used since SSO, TODO: remove
    result = yield callExternalAction('login', Api.login, action)
    if (result && result['access_token']) {
      token = result['access_token']
    }
  }

  if (token) {
    const username = action.payload.credentials.username
    // persistTokenToSessionStorage({ token, username })
    yield put(loginSuccessful({ token, username }))

    const oVirtMeta = yield callExternalAction('getOvirtApiMeta', Api.getOvirtApiMeta, action)
    if (!oVirtMeta['product_info']) { // REST API call failed
      yield put(yield put(loadInProgress({ value: false })))
    } else {
      if (yield checkOvirtApiVersion(oVirtMeta)) {
        yield put(getAllVms({ shallowFetch: false }))
      } else { // oVirt API of incompatible version
        console.error('oVirt api version check failed')
        yield put(failedExternalAction({
          message: composeIncompatibleOVirtApiVersionMessage(oVirtMeta),
          shortMessage: 'oVirt API version check failed' }))
        yield put(yield put(loadInProgress({ value: false })))
      }
    }
  } else {
    yield put(loginFailed({
      errorCode: result['error_code'] ? result['error_code'] : 'no_access',
      message: result['error'] ? (result.error['statusText'] ? result.error['statusText'] : JSON.stringify(result['error'])) : 'Login Failed',
    }))
    yield put(yield put(loadInProgress({ value: false })))
  }
}

function composeIncompatibleOVirtApiVersionMessage (oVirtMeta) {
  const requested = `${Product.ovirtApiVersionRequired.major}.${Product.ovirtApiVersionRequired.minor}`
  let found
  if (!(oVirtMeta && oVirtMeta['product_info'] && oVirtMeta['product_info']['version'] &&
    oVirtMeta['product_info']['version']['major'] && oVirtMeta['product_info']['version']['minor'])) {
    found = JSON.stringify(oVirtMeta)
  } else {
    const version = oVirtMeta['product_info']['version']
    found = `${version.major}.${version.minor}`
  }
  return `oVirt API version requested >= ${requested}, but ${found} found`
}

function compareVersion (actual, required) {
  logDebug(`compareVersion(), actual=${JSON.stringify(actual)}, required=${JSON.stringify(required)}`)

  // assuming backward compatibility of oVirt API
  if (actual.major >= required.major) {
    if (actual.major === required.major) {
      if (actual.minor < required.minor) {
        return false
      }
    }
    return true
  }
  return false
}

function* checkOvirtApiVersion (oVirtMeta) {
  if (!(oVirtMeta && oVirtMeta['product_info'] && oVirtMeta['product_info']['version'] &&
    oVirtMeta['product_info']['version']['major'] && oVirtMeta['product_info']['version']['minor'])) {
    console.error('Incompatible oVirt API version: ', oVirtMeta)
    yield put(setOvirtApiVersion({ passed: false, ...oVirtMeta }))
    return false
  }

  const actual = oVirtMeta['product_info']['version']

  const required = Product.ovirtApiVersionRequired
  const passed = compareVersion({ major: parseInt(actual.major), minor: parseInt(actual.minor) }, required)

  yield put(setOvirtApiVersion({ passed, ...actual }))

  return passed
}

/*
function* onLoginSuccessful () {
  const redirectUrl = store.getState().router
  browserHistory.replace(redirectUrl)
}
*/

function* logout () {
  window.location.href = `${AppConfiguration.applicationURL}/sso/logout`
//  clearTokenFromSessionStorage()
//  browserHistory.replace('/login')
}

function* fetchUnknwonIconsForVms ({ vms }) {
  // unique iconIds from all vms
  const vmIconIds = new Set(vms.map(vm => vm.icons.small.id))
  vms.map(vm => vm.icons.large.id).forEach(id => vmIconIds.add(id))

  // reduce to just unknown
  const allKnownIcons = Selectors.getAllIcons()
  const iconIds = [...vmIconIds].filter(id => !allKnownIcons.get(id))

  yield * foreach(iconIds, function* (iconId) {
    yield fetchIcon({ iconId })
  })
}

function* fetchIcon ({ iconId }) {
  if (iconId) {
    const icon = yield callExternalAction('icon', Api.icon, { payload: { id: iconId } })
    if (icon['media_type'] && icon['data']) {
      yield put(updateIcons({ icons: [Api.iconToInternal({ icon })] }))
    }
  }
}

function* fetchAllVms (action) {
  const { shallowFetch } = action.payload

  // TODO: paging: split this call to a loop per up to 25 vms
  const allVms = yield callExternalAction('getAllVms', Api.getAllVms, action)

  if (allVms && allVms['vm']) { // array
    const internalVms = allVms.vm.map(vm => Api.vmToInternal({ vm }))

    const vmIdsToPreserve = internalVms.map(vm => vm.id)
    yield put(removeMissingVms({ vmIdsToPreserve }))

    yield put(updateVms({ vms: internalVms, copySubResources: true }))

    // TODO: is removing of icons needed? I.e. when icon is removed or changed on the server
    yield fetchUnknwonIconsForVms({ vms: internalVms })

    if (!shallowFetch) {
      yield fetchConsoleMetadatas({ vms: internalVms })
      yield fetchDisks({ vms: internalVms })
    } else {
      logDebug('fetchAllVms() shallow fetch requested - skipping other resources')
    }
  }

  yield put(loadInProgress({ value: false }))
  yield put(persistState())
}

function* fetchDisks ({ vms }) {
  yield * foreach(vms, function* (vm) {
    const vmId = vm.id
    const disks = yield fetchVmDisks({ vmId })
    if (disks && disks.length > 0) {
      yield put(setVmDisks({ vmId, disks }))
    }
  })
}

function* fetchConsoleMetadatas ({ vms }) {
  yield * foreach(vms, function* (vm) {
    const consolesInternal = yield fetchConsoleVmMeta({ vmId: vm.id })
    yield put(setVmConsoles({ vmId: vm.id, consoles: consolesInternal }))
  })
}

function* fetchSingleVm (action) {
  const vm = yield callExternalAction('getVm', Api.getVm, action, true)

  if (vm && vm.id) {
    const internalVm = Api.vmToInternal({ vm })

    internalVm.disks = yield fetchVmDisks({ vmId: internalVm.id })
    internalVm.consoles = yield fetchConsoleVmMeta({ vmId: internalVm.id })

    yield put(updateVms({ vms: [internalVm] }))
  } else {
    if (vm && vm.error && vm.error.status === 404) {
      yield put(removeVms({ vmIds: [action.payload.vmId] }))
    }
  }
}

function* fetchVmDisks ({ vmId }) {
  const diskattachments = yield callExternalAction('diskattachments', Api.diskattachments, { payload: { vmId } })

  if (diskattachments && diskattachments['disk_attachment']) { // array
    const internalDisks = []
    yield * foreach(diskattachments['disk_attachment'], function* (attachment) {
      const diskId = attachment.disk.id
      const disk = yield callExternalAction('disk', Api.disk, { payload: { diskId } })
      internalDisks.push(Api.diskToInternal({ disk, attachment }))
    })
    return internalDisks
  }
  return []
}

function* startProgress ({ vmId, name }) {
  yield put(vmActionInProgress({ vmId, name, started: true }))
}

function* stopProgress ({ vmId, name, result }) {
  if (result && result.status === 'complete') {
      // do not call 'end of in progress' if successful,
      // since UI will be updated by refresh
    yield delay(5 * 1000)
    yield fetchSingleVm(getSingleVm({ vmId }))
    yield delay(30 * 1000)
    yield fetchSingleVm(getSingleVm({ vmId }))
  }

  yield put(vmActionInProgress({ vmId, name, started: false }))
}

function* shutdownVm (action) {
  yield startProgress({ vmId: action.payload.vmId, name: 'shutdown' })
  const result = yield callExternalAction('shutdown', Api.shutdown, action)
  yield stopProgress({ vmId: action.payload.vmId, name: 'shutdown', result })
}

function* restartVm (action) {
  yield startProgress({ vmId: action.payload.vmId, name: 'restart' })
  const result = yield callExternalAction('restart', Api.restart, action)
  yield stopProgress({ vmId: action.payload.vmId, name: 'restart', result })
}

function* suspendVm (action) {
  yield startProgress({ vmId: action.payload.vmId, name: 'suspend' })
  const result = yield callExternalAction('suspend', Api.suspend, action)
  yield stopProgress({ vmId: action.payload.vmId, name: 'suspend', result })
}

function* startVm (action) {
  yield startProgress({ vmId: action.payload.vmId, name: 'start' })
  const result = yield callExternalAction('start', Api.start, action)
  // TODO: check status at refresh --> conditional refresh wait_for_launch
  yield stopProgress({ vmId: action.payload.vmId, name: 'start', result })
}

function* fetchConsoleVmMeta ({ vmId }) {
  const consoles = yield callExternalAction('consoles', Api.consoles, { action: 'INTERNAL_CONSOLES', payload: { vmId } })

  if (consoles && consoles['graphics_console']) { // && consoles['graphics_console'].length > 0) {
    return Api.consolesToInternal({ consoles })
  }
  return []
}

function* getConsoleVm (action) {
  let { vmId, consoleId } = action.payload

  if (!consoleId) {
    yield put(vmActionInProgress({ vmId, name: 'getConsole', started: true }))
    const consolesInternal = yield fetchConsoleVmMeta({ vmId }) // refresh metadata
    yield put(setVmConsoles({ vmId, consoles: consolesInternal }))
    yield put(vmActionInProgress({ vmId, name: 'getConsole', started: false }))

    // TODO: choose user default over just 'SPICE'
    if (consolesInternal && consolesInternal.length > 0) {
      let console = consolesInternal.find(c => c.protocol === 'spice') || consolesInternal[0]
      consoleId = console.id
    }
  }

  if (consoleId) {
    const data = yield callExternalAction('console', Api.console, { action: 'INTERNAL_CONSOLE', payload: { vmId, consoleId } })
    if (data.error === undefined) {
      fileDownload({ data, fileName: 'console.vv', mimeType: 'application/x-virt-viewer' })
    }
  }
}

function* selectVmDetail (action) {
  yield put(setVmDetailToShow({ vmId: action.payload.vmId }))
  yield fetchSingleVm(getSingleVm({ vmId: action.payload.vmId }))
}

function* schedulerPerMinute (action) {
  logDebug('Starting schedulerPerMinute() scheduler')

  // TODO: do we need to stop the loop? Consider takeLatest in the rootSaga 'restarts' the loop if needed
  while (true) {
    yield delay(60 * 1000) // 1 minute
    logDebug('schedulerPerMinute() event')

    const oVirtVersion = Selectors.getOvirtVersion()
    if (oVirtVersion.passed) {
      // Actions to be executed no more than once per minute:
      // TODO: allow user to enable/disable the autorefresh
      yield put(getAllVms({ shallowFetch: true }))
    } else {
      logDebug('schedulerPerMinute() event skipped since oVirt API version does not match')
    }
  }
}

export function *rootSaga () {
  yield [
    takeEvery(LOGIN, login),
    // takeEvery('LOGIN_SUCCESSFUL', onLoginSuccessful),
    takeEvery(LOGOUT, logout),
    takeLatest(GET_ALL_VMS, fetchAllVms),
    takeLatest(PERSIST_STATE, persistStateSaga),

    takeEvery(SHUTDOWN_VM, shutdownVm),
    takeEvery(RESTART_VM, restartVm),
    takeEvery(START_VM, startVm),
    takeEvery(GET_CONSOLE_VM, getConsoleVm),
    takeEvery(SUSPEND_VM, suspendVm),

    takeEvery(SELECT_VM_DETAIL, selectVmDetail),

    takeLatest(SCHEDULER__1_MIN, schedulerPerMinute),
  ]
}

// TODO: translate
// TODO: move to ovirt-ui-actions
const shortMessages = {
  'START_VM': 'Failed to start the VM',
  'RESTART_VM': 'Failed to restart the VM',
  'SHUTDOWN_VM': 'Failed to shutdown the VM',
  'GET_CONSOLE_VM': 'Failed to get the VM console',
  'SUSPEND_VM': 'Failed to suspend the VM',
}

function shortErrorMessage ({ action }) {
  return shortMessages[action.type] ? shortMessages[action.type] : `${action.type} failed` // TODO: translate
}
<|endoftext|>


<commit_before>/**
 * Downscale canvas or image element
 * @param {CanvasElement | ImageElement} canvasOrImage
 * @param {Number | Object} scale
 * @param {CanvasElement} opt_destinationCanvas
 *                         if undefined, original canvas is rewriten
 * @return {CanvasElement}
 */
function scale (canvasOrImage, scale, opt_destinationCanvas) {
	var canvas
	if (canvasOrImage.src) {
		canvas = document.createElement('canvas')
		canvas.width = canvasOrImage.naturalWidth
		canvas.height = canvasOrImage.naturalHeight
		canvas.getContext('2d').drawImage(canvasOrImage, 0, 0)
	} else {
		canvas = canvasOrImage
	}
	var sw = canvas.width, sh = canvas.height
		, dw, dh
		, scaleX, scaleY, scaleXY
		, sourceBuffer = canvas.getContext('2d').getImageData(0, 0, sw, sh).data
	if (canvasOrImage.src || !opt_destinationCanvas) {
		canvas.clearRect(0, 0, sw, sh)
	}
	if (typeof scale === 'object') {
		if (scale.width) {
			dw = scale.width + 0.5 | 0
			dh = scale.height + 0.5 | 0
		} else {
			dw = sw * scale.scaleX + 0.5 | 0
			dh = sh * scale.scaleY + 0.5 | 0
		}
	} else {
		dw = scale * sw + 0.5 | 0
		dh = scale * sh + 0.5 | 0
	}
	scaleX = dw / sw
	scaleY = dh / sh
	scaleXY = scaleX * scaleY
	var dw4 = dw << 2
		,	sw4 = sw << 2
		, sx, sy, sindex = 0
		, dx, dy, dyindex, dindex = 0
		, idx, idy, isx, isy
		, w, wx, nwx, wy, nwy
		, crossX, crossY
		, dwh4 = dw4 * dh
		, tmpBuffer
		, r, g, b, a
		,	isx4
		,	dsy, dsx
		, newCanvas, imageData, byteBuffer
		,	TIMES = 255.99 / 255
		, row0, row1, row2, row3
		, col0, col1, col2, col3

	function getNewCanvas() {
		// CREATE downscale canvas
		var newCanvas
		if (typeof opt_destinationCanvas === 'object') {
			newCanvas = opt_destinationCanvas
		} else if (!opt_destinationCanvas || canvasOrImage.src) {
			newCanvas = canvas
		} else {
			newCanvas = document.createElement('canvas')
		}
		newCanvas.width = dw
		newCanvas.height = dh
		return newCanvas
	}
	function getImageData(canvas, tmpBuffer) {
		if (!tmpBuffer) {
			return canvas.getContext('2d').getImageData(0, 0, dw, dh)
		} else {
			var imageData = newCanvas.getContext('2d').getImageData(0, 0, dw, dh)
				, byteBuffer = imageData.data
				,	dindex
			for (dindex = 0; dindex < dwh4; dindex += 4) {
				byteBuffer[dindex    ] = tmpBuffer[dindex    ] * TIMES | 0
				byteBuffer[dindex + 1] = tmpBuffer[dindex + 1] * TIMES | 0
				byteBuffer[dindex + 2] = tmpBuffer[dindex + 2] * TIMES | 0
				byteBuffer[dindex + 3] = HAS_ALPHA ? tmpBuffer[dindex + 3] * TIMES | 0 : 255
			}
			delete tmpBuffer
			return imageData
		}
	}

	if (scaleX > 1 || scaleY > 1) {
		// UPSCALE by bicubic
		function bicubic (t, a, b, c, d) {
			return 0.5 * (c - a + (2 * a - 5 * b + 4 * c - d + (3 * (b - c) + d - a) * t) * t) * t + b
		}
		newCanvas = getNewCanvas()
		imageData = getImageData(newCanvas)
		byteBuffer = imageData.data
		for (dy = 0; dy < dh; dy++) {
			sy = dy / scaleY
			isy = sy | 0
			dsy = sy - isy
			row1 = isy * sw4
			row0 = isy < 1 ? row1 : row1 - sw4
			if (isy < sh - 2) {
				row2 = row1 + sw4
				row3 = (isy + 2) * sw4
			} else {
				row2 = isy > sh - 2 ? row1 : row1 + sw4
				row3 = row2
			}

			for (dx = 0; dx < dw; dx++, dindex += 4) {
				sx = dx / scaleX
				isx = sx | 0
				dsx = sx - isx
				col1 = isx4 = isx << 2
				col0 = isx < 1 ? col1 : isx4 - 4
				if (isx < sw - 2) {
					col2 = isx4 + 4
					col3 = isx4 + 8
				} else {
					col2 = isx > sw - 2 ? col1 : isx4 + 4
					col3 = col2
				}

				r = bicubic(dsy,
							bicubic(dsx
							,	sourceBuffer[row0 + col0]
							,	sourceBuffer[row0 + col1]
							,	sourceBuffer[row0 + col2]
							,	sourceBuffer[row0 + col3]
							)
						,	bicubic(dsx
							,	sourceBuffer[row1 + col0]
							,	sourceBuffer[row1 + col1]
							,	sourceBuffer[row1 + col2]
							,	sourceBuffer[row1 + col3]
							)
						,	bicubic(dsx
							,	sourceBuffer[row2 + col0]
							,	sourceBuffer[row2 + col1]
							,	sourceBuffer[row2 + col2]
							,	sourceBuffer[row2 + col3]
							)
						,	bicubic(dsx
							,	sourceBuffer[row3 + col0]
							,	sourceBuffer[row3 + col1]
							,	sourceBuffer[row3 + col2]
							,	sourceBuffer[row3 + col3]
							)
						) * TIMES | 0
				g = bicubic(dsy,
							bicubic(dsx
							,	sourceBuffer[row0 + col0 + 1]
							,	sourceBuffer[row0 + col1 + 1]
							,	sourceBuffer[row0 + col2 + 1]
							,	sourceBuffer[row0 + col3 + 1]
							)
						,	bicubic(dsx
							,	sourceBuffer[row1 + col0 + 1]
							,	sourceBuffer[row1 + col1 + 1]
							,	sourceBuffer[row1 + col2 + 1]
							,	sourceBuffer[row1 + col3 + 1]
							)
						,	bicubic(dsx
							,	sourceBuffer[row2 + col0 + 1]
							,	sourceBuffer[row2 + col1 + 1]
							,	sourceBuffer[row2 + col2 + 1]
							,	sourceBuffer[row2 + col3 + 1]
							)
						,	bicubic(dsx
							,	sourceBuffer[row3 + col0 + 1]
							,	sourceBuffer[row3 + col1 + 1]
							,	sourceBuffer[row3 + col2 + 1]
							,	sourceBuffer[row3 + col3 + 1]
							)
						) * TIMES | 0
				b = bicubic(dsy,
							bicubic(dsx
							,	sourceBuffer[row0 + col0 + 2]
							,	sourceBuffer[row0 + col1 + 2]
							,	sourceBuffer[row0 + col2 + 2]
							,	sourceBuffer[row0 + col3 + 2]
							)
						,	bicubic(dsx
							,	sourceBuffer[row1 + col0 + 2]
							,	sourceBuffer[row1 + col1 + 2]
							,	sourceBuffer[row1 + col2 + 2]
							,	sourceBuffer[row1 + col3 + 2]
							)
						,	bicubic(dsx
							,	sourceBuffer[row2 + col0 + 2]
							,	sourceBuffer[row2 + col1 + 2]
							,	sourceBuffer[row2 + col2 + 2]
							,	sourceBuffer[row2 + col3 + 2]
							)
						,	bicubic(dsx
							,	sourceBuffer[row3 + col0 + 2]
							,	sourceBuffer[row3 + col1 + 2]
							,	sourceBuffer[row3 + col2 + 2]
							,	sourceBuffer[row3 + col3 + 2]
							)
						) * TIMES | 0

				byteBuffer[dindex    ] = r >= 0 ? r < 256 ? r : 255 : 0
				byteBuffer[dindex + 1] = g >= 0 ? g < 256 ? g : 255 : 0
				byteBuffer[dindex + 2] = b >= 0 ? b < 256 ? b : 255 : 0

				if (HAS_ALPHA) {
					a = bicubic(dsy,
							bicubic(dsx
							,	sourceBuffer[row0 + col0 + 3]
							,	sourceBuffer[row0 + col1 + 3]
							,	sourceBuffer[row0 + col2 + 3]
							,	sourceBuffer[row0 + col3 + 3]
							)
						,	bicubic(dsx
							,	sourceBuffer[row1 + col0 + 3]
							,	sourceBuffer[row1 + col1 + 3]
							,	sourceBuffer[row1 + col2 + 3]
							,	sourceBuffer[row1 + col3 + 3]
							)
						,	bicubic(dsx
							,	sourceBuffer[row2 + col0 + 3]
							,	sourceBuffer[row2 + col1 + 3]
							,	sourceBuffer[row2 + col2 + 3]
							,	sourceBuffer[row2 + col3 + 3]
							)
						,	bicubic(dsx
							,	sourceBuffer[row3 + col0 + 3]
							,	sourceBuffer[row3 + col1 + 3]
							,	sourceBuffer[row3 + col2 + 3]
							,	sourceBuffer[row3 + col3 + 3]
							)
						) * TIMES | 0
					byteBuffer[dindex + 3] = a >= 0 ? a < 256 ? a : 255 : 0
				} else {
					byteBuffer[dindex + 3] = 255
				}
			}
		}
	} else {
		// DOWNSCALE
		if (window.Float32Array) {
			tmpBuffer = new Float32Array(dwh4)
		} else {
			tmpBuffer = []
			for (var i = 0; i < dwh4; ++i) {
				tmpBuffer[i] = 0
			}
		}
		// CREATE float buffer
		for (sy = 0; sy < sh; sy++) {
			dy = sy * scaleY
			idy = dy | 0
			dyindex = idy * dw4
			crossY = (!!((idy - (dy + scaleY | 0)) * (sh - 1 - sy))) << 1
			if (crossY) {
				wy = idy + 1 - dy
				nwy = dy + scaleY - idy - 1
			}
			for (sx = 0; sx < sw; sx++, sindex += 4) {
				dx = sx * scaleX
				idx = dx | 0
				dindex = dyindex + (idx << 2)
				crossX = !!((idx - (dx + scaleX | 0)) * (sw - 1 - sx))
				if (crossX) {
					wx = idx + 1 - dx
					nwx = dx + scaleX - idx - 1
				}
				r = sourceBuffer[sindex    ]
				g = sourceBuffer[sindex + 1]
				b = sourceBuffer[sindex + 2]
				if (HAS_ALPHA) a = sourceBuffer[sindex + 3]
				switch (crossX + crossY) {
					case 0:
						tmpBuffer[dindex    ] += r * scaleXY
						tmpBuffer[dindex + 1] += g * scaleXY
						tmpBuffer[dindex + 2] += b * scaleXY
						if (HAS_ALPHA) tmpBuffer[dindex + 3] += a * scaleXY
						break
					case 1:
						w = wx * scaleY
						tmpBuffer[dindex    ] += r * w
						tmpBuffer[dindex + 1] += g * w
						tmpBuffer[dindex + 2] += b * w
						if (HAS_ALPHA) tmpBuffer[dindex + 3] += a * w
						w = nwx * scaleY
						tmpBuffer[dindex + 4] += r * w
						tmpBuffer[dindex + 5] += g * w
						tmpBuffer[dindex + 6] += b * w
						if (HAS_ALPHA) tmpBuffer[dindex + 7] += a * w
						break
					case 2:
						w = scaleX * wy
						tmpBuffer[dindex    ] += r * w
						tmpBuffer[dindex + 1] += g * w
						tmpBuffer[dindex + 2] += b * w
						if (HAS_ALPHA) tmpBuffer[dindex + 3] += a * w
						w = scaleX * nwy
						dindex += dw4
						tmpBuffer[dindex    ] += r * w
						tmpBuffer[dindex + 1] += g * w
						tmpBuffer[dindex + 2] += b * w
						if (HAS_ALPHA) tmpBuffer[dindex + 3] += a * w
						break
					default:
						w = wx * wy
						tmpBuffer[dindex    ] += r * w
						tmpBuffer[dindex + 1] += g * w
						tmpBuffer[dindex + 2] += b * w
						if (HAS_ALPHA) tmpBuffer[dindex + 3] += a * w
						w = nwx * wy
						tmpBuffer[dindex + 4] += r * w
						tmpBuffer[dindex + 5] += g * w
						tmpBuffer[dindex + 6] += b * w
						if (HAS_ALPHA) tmpBuffer[dindex + 7] += a * w
						w = wx * nwy
						dindex += dw4
						tmpBuffer[dindex    ] += r * w
						tmpBuffer[dindex + 1] += g * w
						tmpBuffer[dindex + 2] += b * w
						if (HAS_ALPHA) tmpBuffer[dindex + 3] += a * w
						w = nwx * nwy
						tmpBuffer[dindex + 4] += r * w
						tmpBuffer[dindex + 5] += g * w
						tmpBuffer[dindex + 6] += b * w
						if (HAS_ALPHA) tmpBuffer[dindex + 7] += a * w
						break
				}
			}
		}
		delete sourceBuffer
		newCanvas = getNewCanvas()
		imageData = getImageData(newCanvas, tmpBuffer)
	}
	newCanvas.getContext('2d').putImageData(imageData, 0, 0)
	return newCanvas
}
<commit_msg>Add support for exports and module.exports<commit_after>/**
 * Downscale canvas or image element
 * @param {CanvasElement|ImageElement} canvasOrImage
 * @param {Number|Object} scale
 * @param {CanvasElement} opt_destinationCanvas
 *                         if undefined, original canvas is rewritten
 * @return {CanvasElement}
 */
(function() {
	function scale (canvasOrImage, scale, opt_destinationCanvas) {
		var canvas
		if (canvasOrImage.src) {
			canvas = document.createElement('canvas')
			canvas.width = canvasOrImage.naturalWidth
			canvas.height = canvasOrImage.naturalHeight
			canvas.getContext('2d').drawImage(canvasOrImage, 0, 0)
		} else {
			canvas = canvasOrImage
		}
		var sw = canvas.width, sh = canvas.height
			, dw, dh
			, scaleX, scaleY, scaleXY
			, sourceBuffer = canvas.getContext('2d').getImageData(0, 0, sw, sh).data
		if (canvasOrImage.src || !opt_destinationCanvas) {
			canvas.clearRect(0, 0, sw, sh)
		}
		if (typeof scale === 'object') {
			if (scale.width) {
				dw = scale.width + 0.5 | 0
				dh = scale.height + 0.5 | 0
			} else {
				dw = sw * scale.scaleX + 0.5 | 0
				dh = sh * scale.scaleY + 0.5 | 0
			}
		} else {
			dw = scale * sw + 0.5 | 0
			dh = scale * sh + 0.5 | 0
		}
		scaleX = dw / sw
		scaleY = dh / sh
		scaleXY = scaleX * scaleY
		var dw4 = dw << 2
			,	sw4 = sw << 2
			, sx, sy, sindex = 0
			, dx, dy, dyindex, dindex = 0
			, idx, idy, isx, isy
			, w, wx, nwx, wy, nwy
			, crossX, crossY
			, dwh4 = dw4 * dh
			, tmpBuffer
			, r, g, b, a
			,	isx4
			,	dsy, dsx
			, newCanvas, imageData, byteBuffer
			,	TIMES = 255.99 / 255
			, row0, row1, row2, row3
			, col0, col1, col2, col3

		function getNewCanvas() {
			// CREATE downscale canvas
			var newCanvas
			if (typeof opt_destinationCanvas === 'object') {
				newCanvas = opt_destinationCanvas
			} else if (!opt_destinationCanvas || canvasOrImage.src) {
				newCanvas = canvas
			} else {
				newCanvas = document.createElement('canvas')
			}
			newCanvas.width = dw
			newCanvas.height = dh
			return newCanvas
		}
		function getImageData(canvas, tmpBuffer) {
			if (!tmpBuffer) {
				return canvas.getContext('2d').getImageData(0, 0, dw, dh)
			} else {
				var imageData = newCanvas.getContext('2d').getImageData(0, 0, dw, dh)
					, byteBuffer = imageData.data
					,	dindex
				for (dindex = 0; dindex < dwh4; dindex += 4) {
					byteBuffer[dindex    ] = tmpBuffer[dindex    ] * TIMES | 0
					byteBuffer[dindex + 1] = tmpBuffer[dindex + 1] * TIMES | 0
					byteBuffer[dindex + 2] = tmpBuffer[dindex + 2] * TIMES | 0
					byteBuffer[dindex + 3] = HAS_ALPHA ? tmpBuffer[dindex + 3] * TIMES | 0 : 255
				}
				delete tmpBuffer
				return imageData
			}
		}

		if (scaleX > 1 || scaleY > 1) {
			// UPSCALE by bicubic
			function bicubic (t, a, b, c, d) {
				return 0.5 * (c - a + (2 * a - 5 * b + 4 * c - d + (3 * (b - c) + d - a) * t) * t) * t + b
			}
			newCanvas = getNewCanvas()
			imageData = getImageData(newCanvas)
			byteBuffer = imageData.data
			for (dy = 0; dy < dh; dy++) {
				sy = dy / scaleY
				isy = sy | 0
				dsy = sy - isy
				row1 = isy * sw4
				row0 = isy < 1 ? row1 : row1 - sw4
				if (isy < sh - 2) {
					row2 = row1 + sw4
					row3 = (isy + 2) * sw4
				} else {
					row2 = isy > sh - 2 ? row1 : row1 + sw4
					row3 = row2
				}

				for (dx = 0; dx < dw; dx++, dindex += 4) {
					sx = dx / scaleX
					isx = sx | 0
					dsx = sx - isx
					col1 = isx4 = isx << 2
					col0 = isx < 1 ? col1 : isx4 - 4
					if (isx < sw - 2) {
						col2 = isx4 + 4
						col3 = isx4 + 8
					} else {
						col2 = isx > sw - 2 ? col1 : isx4 + 4
						col3 = col2
					}

					r = bicubic(dsy,
								bicubic(dsx
								,	sourceBuffer[row0 + col0]
								,	sourceBuffer[row0 + col1]
								,	sourceBuffer[row0 + col2]
								,	sourceBuffer[row0 + col3]
								)
							,	bicubic(dsx
								,	sourceBuffer[row1 + col0]
								,	sourceBuffer[row1 + col1]
								,	sourceBuffer[row1 + col2]
								,	sourceBuffer[row1 + col3]
								)
							,	bicubic(dsx
								,	sourceBuffer[row2 + col0]
								,	sourceBuffer[row2 + col1]
								,	sourceBuffer[row2 + col2]
								,	sourceBuffer[row2 + col3]
								)
							,	bicubic(dsx
								,	sourceBuffer[row3 + col0]
								,	sourceBuffer[row3 + col1]
								,	sourceBuffer[row3 + col2]
								,	sourceBuffer[row3 + col3]
								)
							) * TIMES | 0
					g = bicubic(dsy,
								bicubic(dsx
								,	sourceBuffer[row0 + col0 + 1]
								,	sourceBuffer[row0 + col1 + 1]
								,	sourceBuffer[row0 + col2 + 1]
								,	sourceBuffer[row0 + col3 + 1]
								)
							,	bicubic(dsx
								,	sourceBuffer[row1 + col0 + 1]
								,	sourceBuffer[row1 + col1 + 1]
								,	sourceBuffer[row1 + col2 + 1]
								,	sourceBuffer[row1 + col3 + 1]
								)
							,	bicubic(dsx
								,	sourceBuffer[row2 + col0 + 1]
								,	sourceBuffer[row2 + col1 + 1]
								,	sourceBuffer[row2 + col2 + 1]
								,	sourceBuffer[row2 + col3 + 1]
								)
							,	bicubic(dsx
								,	sourceBuffer[row3 + col0 + 1]
								,	sourceBuffer[row3 + col1 + 1]
								,	sourceBuffer[row3 + col2 + 1]
								,	sourceBuffer[row3 + col3 + 1]
								)
							) * TIMES | 0
					b = bicubic(dsy,
								bicubic(dsx
								,	sourceBuffer[row0 + col0 + 2]
								,	sourceBuffer[row0 + col1 + 2]
								,	sourceBuffer[row0 + col2 + 2]
								,	sourceBuffer[row0 + col3 + 2]
								)
							,	bicubic(dsx
								,	sourceBuffer[row1 + col0 + 2]
								,	sourceBuffer[row1 + col1 + 2]
								,	sourceBuffer[row1 + col2 + 2]
								,	sourceBuffer[row1 + col3 + 2]
								)
							,	bicubic(dsx
								,	sourceBuffer[row2 + col0 + 2]
								,	sourceBuffer[row2 + col1 + 2]
								,	sourceBuffer[row2 + col2 + 2]
								,	sourceBuffer[row2 + col3 + 2]
								)
							,	bicubic(dsx
								,	sourceBuffer[row3 + col0 + 2]
								,	sourceBuffer[row3 + col1 + 2]
								,	sourceBuffer[row3 + col2 + 2]
								,	sourceBuffer[row3 + col3 + 2]
								)
							) * TIMES | 0

					byteBuffer[dindex    ] = r >= 0 ? r < 256 ? r : 255 : 0
					byteBuffer[dindex + 1] = g >= 0 ? g < 256 ? g : 255 : 0
					byteBuffer[dindex + 2] = b >= 0 ? b < 256 ? b : 255 : 0

					if (HAS_ALPHA) {
						a = bicubic(dsy,
								bicubic(dsx
								,	sourceBuffer[row0 + col0 + 3]
								,	sourceBuffer[row0 + col1 + 3]
								,	sourceBuffer[row0 + col2 + 3]
								,	sourceBuffer[row0 + col3 + 3]
								)
							,	bicubic(dsx
								,	sourceBuffer[row1 + col0 + 3]
								,	sourceBuffer[row1 + col1 + 3]
								,	sourceBuffer[row1 + col2 + 3]
								,	sourceBuffer[row1 + col3 + 3]
								)
							,	bicubic(dsx
								,	sourceBuffer[row2 + col0 + 3]
								,	sourceBuffer[row2 + col1 + 3]
								,	sourceBuffer[row2 + col2 + 3]
								,	sourceBuffer[row2 + col3 + 3]
								)
							,	bicubic(dsx
								,	sourceBuffer[row3 + col0 + 3]
								,	sourceBuffer[row3 + col1 + 3]
								,	sourceBuffer[row3 + col2 + 3]
								,	sourceBuffer[row3 + col3 + 3]
								)
							) * TIMES | 0
						byteBuffer[dindex + 3] = a >= 0 ? a < 256 ? a : 255 : 0
					} else {
						byteBuffer[dindex + 3] = 255
					}
				}
			}
		} else {
			// DOWNSCALE
			if (window.Float32Array) {
				tmpBuffer = new Float32Array(dwh4)
			} else {
				tmpBuffer = []
				for (var i = 0; i < dwh4; ++i) {
					tmpBuffer[i] = 0
				}
			}
			// CREATE float buffer
			for (sy = 0; sy < sh; sy++) {
				dy = sy * scaleY
				idy = dy | 0
				dyindex = idy * dw4
				crossY = (!!((idy - (dy + scaleY | 0)) * (sh - 1 - sy))) << 1
				if (crossY) {
					wy = idy + 1 - dy
					nwy = dy + scaleY - idy - 1
				}
				for (sx = 0; sx < sw; sx++, sindex += 4) {
					dx = sx * scaleX
					idx = dx | 0
					dindex = dyindex + (idx << 2)
					crossX = !!((idx - (dx + scaleX | 0)) * (sw - 1 - sx))
					if (crossX) {
						wx = idx + 1 - dx
						nwx = dx + scaleX - idx - 1
					}
					r = sourceBuffer[sindex    ]
					g = sourceBuffer[sindex + 1]
					b = sourceBuffer[sindex + 2]
					if (HAS_ALPHA) a = sourceBuffer[sindex + 3]
					switch (crossX + crossY) {
						case 0:
							tmpBuffer[dindex    ] += r * scaleXY
							tmpBuffer[dindex + 1] += g * scaleXY
							tmpBuffer[dindex + 2] += b * scaleXY
							if (HAS_ALPHA) tmpBuffer[dindex + 3] += a * scaleXY
							break
						case 1:
							w = wx * scaleY
							tmpBuffer[dindex    ] += r * w
							tmpBuffer[dindex + 1] += g * w
							tmpBuffer[dindex + 2] += b * w
							if (HAS_ALPHA) tmpBuffer[dindex + 3] += a * w
							w = nwx * scaleY
							tmpBuffer[dindex + 4] += r * w
							tmpBuffer[dindex + 5] += g * w
							tmpBuffer[dindex + 6] += b * w
							if (HAS_ALPHA) tmpBuffer[dindex + 7] += a * w
							break
						case 2:
							w = scaleX * wy
							tmpBuffer[dindex    ] += r * w
							tmpBuffer[dindex + 1] += g * w
							tmpBuffer[dindex + 2] += b * w
							if (HAS_ALPHA) tmpBuffer[dindex + 3] += a * w
							w = scaleX * nwy
							dindex += dw4
							tmpBuffer[dindex    ] += r * w
							tmpBuffer[dindex + 1] += g * w
							tmpBuffer[dindex + 2] += b * w
							if (HAS_ALPHA) tmpBuffer[dindex + 3] += a * w
							break
						default:
							w = wx * wy
							tmpBuffer[dindex    ] += r * w
							tmpBuffer[dindex + 1] += g * w
							tmpBuffer[dindex + 2] += b * w
							if (HAS_ALPHA) tmpBuffer[dindex + 3] += a * w
							w = nwx * wy
							tmpBuffer[dindex + 4] += r * w
							tmpBuffer[dindex + 5] += g * w
							tmpBuffer[dindex + 6] += b * w
							if (HAS_ALPHA) tmpBuffer[dindex + 7] += a * w
							w = wx * nwy
							dindex += dw4
							tmpBuffer[dindex    ] += r * w
							tmpBuffer[dindex + 1] += g * w
							tmpBuffer[dindex + 2] += b * w
							if (HAS_ALPHA) tmpBuffer[dindex + 3] += a * w
							w = nwx * nwy
							tmpBuffer[dindex + 4] += r * w
							tmpBuffer[dindex + 5] += g * w
							tmpBuffer[dindex + 6] += b * w
							if (HAS_ALPHA) tmpBuffer[dindex + 7] += a * w
							break
					}
				}
			}
			delete sourceBuffer
			newCanvas = getNewCanvas()
			imageData = getImageData(newCanvas, tmpBuffer)
		}
		newCanvas.getContext('2d').putImageData(imageData, 0, 0)
		return newCanvas
	}

	// exports
	if(exports){
		if(module && module.exports){
			exports = module.exports = scale
		}
		exports.scale = scale
	} else {
		this.scale = scale
	}
}.call(this))
<|endoftext|>


<commit_before>import find from 'lodash.find';

let mediaQueries = {
  between(val1, val2) {
    return window.matchMedia(`(min-width: ${val1}px) and
                              (max-width: ${val2 - 1}px)`);
  },
  atLeast(val) {
    return window.matchMedia(`(min-width: ${val}px)`);
  },
  atMost(val) {
    return window.matchMedia(`(max-width: ${val - 1}px)`);
  }
};

let query = function(bp, nextBp) {
  return {
    is: typeof nextBp === 'number'
          ? mediaQueries.between(bp, nextBp)
          : mediaQueries.atLeast(bp),
    atLeast: mediaQueries.atLeast(bp),
    atMost: mediaQueries.atMost(nextBp || Number.MAX_VALUE)
  };
};

let Breakjs = function(bpEntries) {
  if (!bpEntries) {
    throw new Error('No breakpoints were defined!');
  }

  let bps = [];
  for (let key in bpEntries) {
    let entry = {name: key, value: bpEntries[key]};

    if (find(bps, bp => bp.value === entry.value)) {
      throw new Error('Breakpoint values must be unique.');
    }

    bps.push(entry);
  }

  let breakpoints = bps
    .sort((a, b) => { return a.value > b.value; })
    .map((bp, index) => {
      if (typeof bp.name !== 'string') {
        throw new Error('Invalid breakpoint name -- should be a string.');
      }

      if (typeof bp.value !== 'number' || bp.value < 0 || bp.value >= 9999) {
        throw new Error(`Invalid breakpoint value for ${bp.name}: ${bp.value}`);
      }

      let breakpoint = {name: bp.name};

      // only query
      if (bps.length === 1) {
        breakpoint.query = query(0, null);
      }

      // last query
      else if (index === bps.length - 1) {
        breakpoint.query = query(bp.value, null);
      }

      // query inbetween
      else {
        breakpoint.query = query(bp.value, bps[index + 1].value);
      }

      return breakpoint;
    });

  function getBreakpoint(breakpointName) {
    let findObj = find(breakpoints, bp => bp.name === breakpointName);

    if (!findObj) {
      throw new Error('invalid breakpoint name');
    }

    return findObj;
  }

  let changeListeners = [];

  return {
    breakpoints: bps,

    /**
     * Check if the current window size is the given size
     * @param  {String} size
     * @return {Boolean}
     */
    is(name) {
      return getBreakpoint(name).query.is.matches;
    },

    /**
     * Check if the current window size at least the given size
     * @param  {String} size
     * @return {Boolean}
     */
    atLeast(name) {
      return getBreakpoint(name).query.atLeast.matches;
    },

    /**
     * Check if the current window size at most the given size
     * @param  {String} size
     * @return {Boolean}
     */
    atMost(name) {
      return getBreakpoint(name).query.atMost.matches;
    },

    current() {
      let findObj = find(breakpoints, bp => bp.query.is.matches);

      if (findObj) {
        return findObj.name;
      }
    },

    addChangeListener(listener) {
      breakpoints.forEach(bp => {
        let changeListener = () => {
          var current = this.current();
          if (current === bp.name) {
            listener(current);
          }
        };

        changeListeners.push({
          original: listener,
          created: changeListener
        });

        bp.query.is.addListener(changeListener);
      });
    },

    removeChangeListener(listener) {
      breakpoints.forEach(bp => {
        let findObj = find(changeListeners, cl => cl.original === listener);

        if (findObj) {
          bp.query.is.removeListener(findObj.created);
          changeListeners.splice(changeListeners.indexOf(findObj), 1);
        }
      });
    }
  };
};

if (typeof window !== 'undefined') {
  window.Breakjs = Breakjs;
}

export default Breakjs;
<commit_msg>replaced some lets with consts<commit_after>import find from 'lodash.find';

const mediaQueries = {
  between(val1, val2) {
    return window.matchMedia(`(min-width: ${val1}px) and
                              (max-width: ${val2 - 1}px)`);
  },
  atLeast(val) {
    return window.matchMedia(`(min-width: ${val}px)`);
  },
  atMost(val) {
    return window.matchMedia(`(max-width: ${val - 1}px)`);
  }
};

const query = function(bp, nextBp) {
  return {
    is: typeof nextBp === 'number'
          ? mediaQueries.between(bp, nextBp)
          : mediaQueries.atLeast(bp),
    atLeast: mediaQueries.atLeast(bp),
    atMost: mediaQueries.atMost(nextBp || Number.MAX_VALUE)
  };
};

const Breakjs = function(bpEntries) {
  if (!bpEntries) {
    throw new Error('No breakpoints were defined!');
  }

  let bps = [];
  for (let key in bpEntries) {
    let entry = {name: key, value: bpEntries[key]};

    if (find(bps, bp => bp.value === entry.value)) {
      throw new Error('Breakpoint values must be unique.');
    }

    bps.push(entry);
  }

  let breakpoints = bps
    .sort((a, b) => { return a.value > b.value; })
    .map((bp, index) => {
      if (typeof bp.name !== 'string') {
        throw new Error('Invalid breakpoint name -- should be a string.');
      }

      if (typeof bp.value !== 'number' || bp.value < 0 || bp.value >= 9999) {
        throw new Error(`Invalid breakpoint value for ${bp.name}: ${bp.value}`);
      }

      let breakpoint = {name: bp.name};

      // only query
      if (bps.length === 1) {
        breakpoint.query = query(0, null);
      }

      // last query
      else if (index === bps.length - 1) {
        breakpoint.query = query(bp.value, null);
      }

      // query inbetween
      else {
        breakpoint.query = query(bp.value, bps[index + 1].value);
      }

      return breakpoint;
    });

  function getBreakpoint(breakpointName) {
    let findObj = find(breakpoints, bp => bp.name === breakpointName);

    if (!findObj) {
      throw new Error('invalid breakpoint name');
    }

    return findObj;
  }

  let changeListeners = [];

  return {
    breakpoints: bps,

    /**
     * Check if the current window size is the given size
     * @param  {String} size
     * @return {Boolean}
     */
    is(name) {
      return getBreakpoint(name).query.is.matches;
    },

    /**
     * Check if the current window size at least the given size
     * @param  {String} size
     * @return {Boolean}
     */
    atLeast(name) {
      return getBreakpoint(name).query.atLeast.matches;
    },

    /**
     * Check if the current window size at most the given size
     * @param  {String} size
     * @return {Boolean}
     */
    atMost(name) {
      return getBreakpoint(name).query.atMost.matches;
    },

    current() {
      let findObj = find(breakpoints, bp => bp.query.is.matches);

      if (findObj) {
        return findObj.name;
      }
    },

    addChangeListener(listener) {
      breakpoints.forEach(bp => {
        let changeListener = () => {
          var current = this.current();
          if (current === bp.name) {
            listener(current);
          }
        };

        changeListeners.push({
          original: listener,
          created: changeListener
        });

        bp.query.is.addListener(changeListener);
      });
    },

    removeChangeListener(listener) {
      breakpoints.forEach(bp => {
        let findObj = find(changeListeners, cl => cl.original === listener);

        if (findObj) {
          bp.query.is.removeListener(findObj.created);
          changeListeners.splice(changeListeners.indexOf(findObj), 1);
        }
      });
    }
  };
};

if (typeof window !== 'undefined') {
  window.Breakjs = Breakjs;
}

export default Breakjs;
<|endoftext|>


<commit_before>import find from 'lodash.find';

const mediaQueries = {
  between(val1, val2) {
    return window.matchMedia(`(min-width: ${val1}px) and
                              (max-width: ${val2 - 1}px)`);
  },
  atLeast(val) {
    return window.matchMedia(`(min-width: ${val}px)`);
  },
  atMost(val) {
    return window.matchMedia(`(max-width: ${val - 1}px)`);
  }
};

const query = function(bp, nextBp) {
  return {
    is: typeof nextBp === 'number'
          ? mediaQueries.between(bp, nextBp)
          : mediaQueries.atLeast(bp),
    atLeast: mediaQueries.atLeast(bp),
    atMost: mediaQueries.atMost(nextBp || Number.MAX_VALUE)
  };
};

const Breakjs = function(bpEntries) {
  if (!bpEntries) {
    throw new Error('No breakpoints were defined!');
  }

  let bps = [];
  for (let key in bpEntries) {
    let entry = {name: key, value: bpEntries[key]};

    if (find(bps, bp => bp.value === entry.value)) {
      throw new Error('Breakpoint values must be unique.');
    }

    bps.push(entry);
  }

  let breakpoints = bps
    .sort((a, b) => { return a.value > b.value; })
    .map((bp, index) => {
      if (typeof bp.name !== 'string') {
        throw new Error('Invalid breakpoint name -- should be a string.');
      }

      if (typeof bp.value !== 'number' || bp.value < 0 || bp.value >= 9999) {
        throw new Error(`Invalid breakpoint value for ${bp.name}: ${bp.value}`);
      }

      let breakpoint = {name: bp.name};

      // only query
      if (bps.length === 1) {
        breakpoint.query = query(0, null);
      }

      // last query
      else if (index === bps.length - 1) {
        breakpoint.query = query(bp.value, null);
      }

      // query inbetween
      else {
        breakpoint.query = query(bp.value, bps[index + 1].value);
      }

      return breakpoint;
    });

  function getBreakpoint(breakpointName) {
    let findObj = find(breakpoints, bp => bp.name === breakpointName);

    if (!findObj) {
      throw new Error('invalid breakpoint name');
    }

    return findObj;
  }

  let changeListeners = [];

  return {
    breakpoints: bps,

    /**
     * Check if the current window size is the given size
     * @param  {String} size
     * @return {Boolean}
     */
    is(name) {
      return getBreakpoint(name).query.is.matches;
    },

    /**
     * Check if the current window size at least the given size
     * @param  {String} size
     * @return {Boolean}
     */
    atLeast(name) {
      return getBreakpoint(name).query.atLeast.matches;
    },

    /**
     * Check if the current window size at most the given size
     * @param  {String} size
     * @return {Boolean}
     */
    atMost(name) {
      return getBreakpoint(name).query.atMost.matches;
    },

    current() {
      let findObj = find(breakpoints, bp => bp.query.is.matches);

      if (findObj) {
        return findObj.name;
      }
    },

    addChangeListener(listener) {
      breakpoints.forEach(bp => {
        let changeListener = () => {
          var current = this.current();
          if (current === bp.name) {
            listener(current);
          }
        };

        changeListeners.push({
          original: listener,
          created: changeListener
        });

        bp.query.is.addListener(changeListener);
      });
    },

    removeChangeListener(listener) {
      breakpoints.forEach(bp => {
        let findObj = find(changeListeners, cl => cl.original === listener);

        if (findObj) {
          bp.query.is.removeListener(findObj.created);
          changeListeners.splice(changeListeners.indexOf(findObj), 1);
        }
      });
    }
  };
};

if (typeof window !== 'undefined') {
  window.Breakjs = Breakjs;
}

export default Breakjs;
<commit_msg>prettified a line break<commit_after>import find from 'lodash.find';

const mediaQueries = {
  between(val1, val2) {
    return window.matchMedia(`(min-width: ${val1}px) and ` +
                             `(max-width: ${val2 - 1}px)`);
  },
  atLeast(val) {
    return window.matchMedia(`(min-width: ${val}px)`);
  },
  atMost(val) {
    return window.matchMedia(`(max-width: ${val - 1}px)`);
  }
};

const query = function(bp, nextBp) {
  return {
    is: typeof nextBp === 'number'
          ? mediaQueries.between(bp, nextBp)
          : mediaQueries.atLeast(bp),
    atLeast: mediaQueries.atLeast(bp),
    atMost: mediaQueries.atMost(nextBp || Number.MAX_VALUE)
  };
};

const Breakjs = function(bpEntries) {
  if (!bpEntries) {
    throw new Error('No breakpoints were defined!');
  }

  let bps = [];
  for (let key in bpEntries) {
    let entry = {name: key, value: bpEntries[key]};

    if (find(bps, bp => bp.value === entry.value)) {
      throw new Error('Breakpoint values must be unique.');
    }

    bps.push(entry);
  }

  let breakpoints = bps
    .sort((a, b) => { return a.value > b.value; })
    .map((bp, index) => {
      if (typeof bp.name !== 'string') {
        throw new Error('Invalid breakpoint name -- should be a string.');
      }

      if (typeof bp.value !== 'number' || bp.value < 0 || bp.value >= 9999) {
        throw new Error(`Invalid breakpoint value for ${bp.name}: ${bp.value}`);
      }

      let breakpoint = {name: bp.name};

      // only query
      if (bps.length === 1) {
        breakpoint.query = query(0, null);
      }

      // last query
      else if (index === bps.length - 1) {
        breakpoint.query = query(bp.value, null);
      }

      // query inbetween
      else {
        breakpoint.query = query(bp.value, bps[index + 1].value);
      }

      return breakpoint;
    });

  function getBreakpoint(breakpointName) {
    let findObj = find(breakpoints, bp => bp.name === breakpointName);

    if (!findObj) {
      throw new Error('invalid breakpoint name');
    }

    return findObj;
  }

  let changeListeners = [];

  return {
    breakpoints: bps,

    /**
     * Check if the current window size is the given size
     * @param  {String} size
     * @return {Boolean}
     */
    is(name) {
      return getBreakpoint(name).query.is.matches;
    },

    /**
     * Check if the current window size at least the given size
     * @param  {String} size
     * @return {Boolean}
     */
    atLeast(name) {
      return getBreakpoint(name).query.atLeast.matches;
    },

    /**
     * Check if the current window size at most the given size
     * @param  {String} size
     * @return {Boolean}
     */
    atMost(name) {
      return getBreakpoint(name).query.atMost.matches;
    },

    current() {
      let findObj = find(breakpoints, bp => bp.query.is.matches);

      if (findObj) {
        return findObj.name;
      }
    },

    addChangeListener(listener) {
      breakpoints.forEach(bp => {
        let changeListener = () => {
          var current = this.current();
          if (current === bp.name) {
            listener(current);
          }
        };

        changeListeners.push({
          original: listener,
          created: changeListener
        });

        bp.query.is.addListener(changeListener);
      });
    },

    removeChangeListener(listener) {
      breakpoints.forEach(bp => {
        let findObj = find(changeListeners, cl => cl.original === listener);

        if (findObj) {
          bp.query.is.removeListener(findObj.created);
          changeListeners.splice(changeListeners.indexOf(findObj), 1);
        }
      });
    }
  };
};

if (typeof window !== 'undefined') {
  window.Breakjs = Breakjs;
}

export default Breakjs;
<|endoftext|>


<commit_before>/**
 * @file Scope management
 */
/*#ifndef(UMD)*/
"use strict";
/*global _gpfMainContext*/ // Main context object
/*exported _gpfResolveScope*/ // Translate the parameter into a valid scope
/*#endif*/

/**
 * Translate the parameter into a valid scope
 *
 * @param {*} scope
 */
function _gpfResolveScope (scope) {
    if (null === scope || "object" !== typeof scope) {
        return _gpfMainContext;
    }
    return scope;
}

/*#ifndef(UMD)*/

gpf.internals._gpfResolveScope = _gpfResolveScope;

/*#endif*/
<commit_msg>Useless<commit_after><|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');

function initWatchVal() {}

function Scope() {
    this.$$watchers = [];
}

Scope.prototype.$watch = function(watchFn, listenerFn) {
    var watcher = {
        watchFn: watchFn,
        listenerFn: listenerFn || function () {},
        last: initWatchVal
    };
    this.$$watchers.push(watcher);
};

Scope.prototype.$digest = function() {
    var that = this;
    _.forEach(this.$$watchers, function(watcher) {
        var newValue = watcher.watchFn(that);
        var oldValue = watcher.last;
        if (newValue !== oldValue) {
            watcher.last = newValue;
            watcher.listenerFn(newValue,
                oldValue === initWatchVal ? newValue : oldValue,
                that);
        }
    });
};

module.exports = Scope;
<commit_msg>jscs fix<commit_after>'use strict';

var _ = require('lodash');

function initWatchVal() {}

function Scope() {
    this.$$watchers = [];
}

Scope.prototype.$watch = function(watchFn, listenerFn) {
    var watcher = {
        watchFn: watchFn,
        listenerFn: listenerFn || function() {},
        last: initWatchVal
    };
    this.$$watchers.push(watcher);
};

Scope.prototype.$digest = function() {
    var that = this;
    _.forEach(this.$$watchers, function(watcher) {
        var newValue = watcher.watchFn(that);
        var oldValue = watcher.last;
        if (newValue !== oldValue) {
            watcher.last = newValue;
            watcher.listenerFn(newValue,
                oldValue === initWatchVal ? newValue : oldValue,
                that);
        }
    });
};

module.exports = Scope;
<|endoftext|>


<commit_before>const webpack = require("webpack");
const fs = require("fs");

const config = {
    entry: "/src/app/entry.js",
    output: {
        path: "/src/dist",
        filename: "bundle.js",
    },
};

const compiler = webpack(config);

compiler.run((err, stats) => {
    if (err) {
        throw err;
    }
    console.log(stats.toString());
    // It should print "Hello world"
    console.log(eval(fs.readFileSync("/src/dist/bundle.js", "utf8"))); // eslint-disable-line no-eval
});<commit_msg>Fix compatibility issues in Firefox and Safari<commit_after>if ("captureStackTrace" in Error === false) {
    Error.captureStackTrace = () => {};
}

const webpack = require("webpack");
const fs = require("fs");

const config = {
    entry: "/src/app/entry.js",
    output: {
        path: "/src/dist",
        filename: "bundle.js",
    },
    // We need to define that because Firefox has a Object.prototype.watch function
    watch: false,
};

const compiler = webpack(config);

compiler.run((err, stats) => {
    if (err) {
        throw err;
    }
    console.log(stats.toString());
    // It should print "Hello world"
    console.log(eval(fs.readFileSync("/src/dist/bundle.js", "utf8"))); // eslint-disable-line no-eval
});
<|endoftext|>


<commit_before>import path from 'path';
import Zip from 'jszip';
import Promise, { all, promisifyAll, resolve } from 'bluebird';
import {
    complement,
    concat,
    contains,
    curry,
    drop,
    equals,
    endsWith,
    filter,
    head,
    identity,
    ifElse,
    is,
    join,
    lensProp,
    map,
    mapObjIndexed,
    merge,
    over,
    propEq,
    replace,
    sort,
    startsWith,
    subtract,
    takeWhile,
    test,
    tryCatch,
    unary,
    union,
    without
} from 'ramda';
import deepmerge from 'deepmerge';
import { emitSuccess, emitWarning } from './input';
import { getProperties } from './vm';
import { compileModulesFromSource, ensureNoImports, inspect } from './module';

const fs = promisifyAll(require('fs'));

const defaultFileOptions = { date: new Date(1149562800000) };
const requiredFiles = ['package.json', 'index.js'];

const localeByFile = drop(8)
    & takeWhile(complement(equals('.')))
    & join('');

/**
 * Converts a list of locale files to pairs containing locale string and content
 *
 * @param {String[]} localeFiles
 * @return {Promise}
 */
function localesToPairs(localeFiles) {
    return all(localeFiles.map(localeFile => fs.readFileAsync(localeFile, 'utf-8')
        .then(JSON.parse)
        .then(json => [localeByFile(localeFile), json])));
}

/**
 * Projects locale for each translatable subfield
 *
 * @param {String} locale
 * @param {Object} config
 * @return {Object}
 */
const project = curry((locale, config) => ({
    title: { [locale]: config.title },
    description: { [locale]: config.description },
    preview: { [locale]: config.preview },
    params: mapObjIndexed(param => merge(param,
        { description: { [locale]: param.description } }), config.params)
}));

/**
 * Lazily runs the extension using all possible listed locales and extracts
 * the meta-data.
 *
 * @param {String} source
 * @param {[(String, *)]} locales
 * @return {Promise}
 */
const runInAllLocales = curry((source, locales) =>
    compileModulesFromSource(source).then(modules =>
        all([['default', {}], ...locales].map(([locale, strings]) =>
            getProperties({ name: `precompile-${locale}`, source }, strings, modules)
                .then(project(locale))))
                .then(ifElse(propEq('length', 1), head, unary(deepmerge.all)))));

/**
 * Creates a meta file where the information about precompilation is stored
 *
 * @param {Object} locales
 * @return {Promise}
 */
function createMetaFile(locales) {
    return fs.writeFileAsync('.meta', JSON.stringify(locales));
}

/**
 * Precompiles linked files, generating a .meta file with all the meta data
 *
 * @param {Object<String, String[]>} { code, files }
 * @return {Promise}
 */
function precompile({ code, files }) {
    return resolve(files)
        .then(filter(test(/^locales(\/|\\)[a-z]{2,3}(_[A-Z]{2})?\.json$/)))
        .then(localesToPairs)
        .then(runInAllLocales(code))
        .then(createMetaFile)
        .thenReturn(['.meta', ...files]);
}

/**
 * Ensures there are missing no files in order to a allow a basic compilation
 * and filter the used modules. It also warns about possible improvements in the
 * extensions
 *
 * @param {String[]} files
 * @return {Promise}
 */
function filterFiles(files) {
    const clearModule = replace(/^\.\//, '');
    const missingFiles = without(files, requiredFiles);
    const hasIcon = contains('icon.png', files);
    const resources = hasIcon ? ['icon.png'] : [];

    if (missingFiles.length > 0) {
        throw new Error(`missing ${missingFiles.join(', ')} from the project`);
    }

    if (!hasIcon) {
        emitWarning('compiling extension without providing an icon.png file');
    }

    return fs.readFileAsync('index.js', 'utf-8')
        .then(inspect)
        .then(over(lensProp('modules'), filter(startsWith('./'))))
        .then(({ code, modules }) => ({
            code, files: union(modules.map(clearModule), concat(resources, requiredFiles)) }));
}

/**
 * Returns all the files in a directory if it exists. Otherwise, return an
 * empty array as fallback (everything inside a promise)
 *
 * @param {String} directory
 * @return {String[]}
 */
function listFiles(directory) {
    return fs.lstatAsync(directory)
        .then(lstat => lstat.isDirectory() ? fs.readdirAsync(directory) : [])
        .catchReturn([]);
}

/**
 * Links autocomplete files
 *
 * @return {Promise}
 */
function linkAutoComplete() {
    return listFiles('autocomplete')
        .then(filter(endsWith('.js')) & map(path.join('autocomplete', _)))
        .tap(files => all(files.map(file => fs.readFileAsync(file)
            .then(ensureNoImports(file)))));
}

/**
 * Links locale files
 *
 * @return {Promise}
 */
function linkLocales() {
    return listFiles('locales')
        .then(filter(test(/^[a-z]{2}(_[A-Z]{2,3})?\.json$/)) & map(path.join('locales', _)))
        .filter(location => fs.readFileAsync(location)
            .then(JSON.parse & is(Object))
            .catchReturn(false))
        .catchReturn([]);
}

/**
 * Links the files to precompilation, including locales and autocomplete
 * scripts. For autocomplete files, ensuring it is a valid script without
 * requires. For locales, filtering true locale files and appending the full
 * qualified name for current files.
 *
 * @param {Object<String, String[]>} { code, files }
 * @return {Promise}
 */
function linkFiles({ code, files }) {
    return all([linkLocales(), linkAutoComplete()])
        .spread(union)
        .then(union(files) & sort(subtract) & (files => ({ code, files })));
}

/**
 * Opens package.json and extrats its contents. Returns a promise containing
 * the file list to be zipped and the package.json content parsed
 *
 * @param {String} dir
 * @return {Promise}
 */
function getProjectName(dir) {
    return fs.readFileAsync(path.join(dir, 'package.json'))
        .then(JSON.parse & _.name)
        .catchThrow(new Error('Failed to parse package.json from the project'));
}

/**
 * Generates a zip package using a node buffer containing the necessary files
 *
 * @param {String} dir
 * @param {String[]} files
 * @param {String} name
 */
const createZip = curry((dir, files) => {
    const zip = new Zip();
    files.forEach(addToZip(zip, dir, _));
    return zip;
});

/**
 * Taking account the -o parameter can be used to specify the output directory,
 * let's deal with it
 *
 * @param {String} customPath
 * @param {String} filename
 * @return {String}
 */
function resolveOutputTarget(customPath, filename) {
    const realPath = path.resolve('.', customPath);

    const getPath = tryCatch(realPath => fs.lstatSync(realPath).isDirectory()
        ? path.join(realPath, filename)
        : realPath
    , identity);

    return getPath(realPath);
}

/**
 * Saves the zip file from buffer to the filesystem
 *
 * @param {String} dir
 * @param {Zip} zip
 * @param {String} name
 */
const saveZip = curry((dir, zip, name) => {
    const target = resolveOutputTarget(dir, `${name}.rung`);

    return new Promise((resolve, reject) => {
        zip.generateNodeStream({ type: 'nodebuffer', streamFiles: true })
            .pipe(fs.createWriteStream(target))
            .on('error', reject)
            .on('finish', ~resolve(target));
    });
});

/**
 * Appends a file or folder to the zip buffer
 *
 * @param {Zip} zip
 * @param {String} dir
 * @param {String} filename
 */
function addToZip(zip, dir, filename) {
    const filePath = path.join(dir, filename);
    const lstat = fs.lstatSync(filePath);

    if (lstat.isFile()) {
        return zip.file(filename, fs.readFileSync(filePath), defaultFileOptions);
    }

    if (lstat.isDirectory()) {
        return map(file => addToZip(zip.folder(filename), filePath, file),
            fs.readdirSync(filePath));
    }
}

/**
 * Precompiles an extension and generates a .rung package
 *
 * @param {Object} args
 */
export default function build(args) {
    const dir = path.resolve('.', args._[1] || '');

    return fs.readdirAsync(dir)
        .then(filterFiles)
        .then(linkFiles)
        .then(precompile)
        .then(createZip(dir))
        .then(zip => all([zip, getProjectName(dir)]))
        .spread(saveZip(args.output || '.'))
        .tap(~emitSuccess('Rung extension compilation'));
}
<commit_msg>No need to lstat. All paths are files<commit_after>import path from 'path';
import Zip from 'jszip';
import Promise, { all, promisifyAll, resolve } from 'bluebird';
import {
    complement,
    concat,
    contains,
    curry,
    drop,
    equals,
    endsWith,
    filter,
    head,
    identity,
    ifElse,
    is,
    join,
    lensProp,
    map,
    mapObjIndexed,
    merge,
    over,
    propEq,
    replace,
    sort,
    startsWith,
    subtract,
    takeWhile,
    test,
    tryCatch,
    unary,
    union,
    without
} from 'ramda';
import deepmerge from 'deepmerge';
import { emitSuccess, emitWarning } from './input';
import { getProperties } from './vm';
import { compileModulesFromSource, ensureNoImports, inspect } from './module';

const fs = promisifyAll(require('fs'));

const defaultFileOptions = { date: new Date(1149562800000) };
const requiredFiles = ['package.json', 'index.js'];

const localeByFile = drop(8)
    & takeWhile(complement(equals('.')))
    & join('');

/**
 * Converts a list of locale files to pairs containing locale string and content
 *
 * @param {String[]} localeFiles
 * @return {Promise}
 */
function localesToPairs(localeFiles) {
    return all(localeFiles.map(localeFile => fs.readFileAsync(localeFile, 'utf-8')
        .then(JSON.parse)
        .then(json => [localeByFile(localeFile), json])));
}

/**
 * Projects locale for each translatable subfield
 *
 * @param {String} locale
 * @param {Object} config
 * @return {Object}
 */
const project = curry((locale, config) => ({
    title: { [locale]: config.title },
    description: { [locale]: config.description },
    preview: { [locale]: config.preview },
    params: mapObjIndexed(param => merge(param,
        { description: { [locale]: param.description } }), config.params)
}));

/**
 * Lazily runs the extension using all possible listed locales and extracts
 * the meta-data.
 *
 * @param {String} source
 * @param {[(String, *)]} locales
 * @return {Promise}
 */
const runInAllLocales = curry((source, locales) =>
    compileModulesFromSource(source).then(modules =>
        all([['default', {}], ...locales].map(([locale, strings]) =>
            getProperties({ name: `precompile-${locale}`, source }, strings, modules)
                .then(project(locale))))
                .then(ifElse(propEq('length', 1), head, unary(deepmerge.all)))));

/**
 * Creates a meta file where the information about precompilation is stored
 *
 * @param {Object} locales
 * @return {Promise}
 */
function createMetaFile(locales) {
    return fs.writeFileAsync('.meta', JSON.stringify(locales));
}

/**
 * Precompiles linked files, generating a .meta file with all the meta data
 *
 * @param {Object<String, String[]>} { code, files }
 * @return {Promise}
 */
function precompile({ code, files }) {
    return resolve(files)
        .then(filter(test(/^locales(\/|\\)[a-z]{2,3}(_[A-Z]{2})?\.json$/)))
        .then(localesToPairs)
        .then(runInAllLocales(code))
        .then(createMetaFile)
        .thenReturn(['.meta', ...files]);
}

/**
 * Ensures there are missing no files in order to a allow a basic compilation
 * and filter the used modules. It also warns about possible improvements in the
 * extensions
 *
 * @param {String[]} files
 * @return {Promise}
 */
function filterFiles(files) {
    const clearModule = replace(/^\.\//, '');
    const missingFiles = without(files, requiredFiles);
    const hasIcon = contains('icon.png', files);
    const resources = hasIcon ? ['icon.png'] : [];

    if (missingFiles.length > 0) {
        throw new Error(`missing ${missingFiles.join(', ')} from the project`);
    }

    if (!hasIcon) {
        emitWarning('compiling extension without providing an icon.png file');
    }

    return fs.readFileAsync('index.js', 'utf-8')
        .then(inspect)
        .then(over(lensProp('modules'), filter(startsWith('./'))))
        .then(({ code, modules }) => ({
            code, files: union(modules.map(clearModule), concat(resources, requiredFiles)) }));
}

/**
 * Returns all the files in a directory if it exists. Otherwise, return an
 * empty array as fallback (everything inside a promise)
 *
 * @param {String} directory
 * @return {String[]}
 */
function listFiles(directory) {
    return fs.lstatAsync(directory)
        .then(lstat => lstat.isDirectory() ? fs.readdirAsync(directory) : [])
        .catchReturn([]);
}

/**
 * Links autocomplete files
 *
 * @return {Promise}
 */
function linkAutoComplete() {
    return listFiles('autocomplete')
        .then(filter(endsWith('.js')) & map(path.join('autocomplete', _)))
        .tap(files => all(files.map(file => fs.readFileAsync(file)
            .then(ensureNoImports(file)))));
}

/**
 * Links locale files
 *
 * @return {Promise}
 */
function linkLocales() {
    return listFiles('locales')
        .then(filter(test(/^[a-z]{2}(_[A-Z]{2,3})?\.json$/)) & map(path.join('locales', _)))
        .filter(location => fs.readFileAsync(location)
            .then(JSON.parse & is(Object))
            .catchReturn(false))
        .catchReturn([]);
}

/**
 * Links the files to precompilation, including locales and autocomplete
 * scripts. For autocomplete files, ensuring it is a valid script without
 * requires. For locales, filtering true locale files and appending the full
 * qualified name for current files.
 *
 * @param {Object<String, String[]>} { code, files }
 * @return {Promise}
 */
function linkFiles({ code, files }) {
    return all([linkLocales(), linkAutoComplete()])
        .spread(union)
        .then(union(files) & sort(subtract) & (files => ({ code, files })));
}

/**
 * Opens package.json and extrats its contents. Returns a promise containing
 * the file list to be zipped and the package.json content parsed
 *
 * @param {String} dir
 * @return {Promise}
 */
function getProjectName(dir) {
    return fs.readFileAsync(path.join(dir, 'package.json'))
        .then(JSON.parse & _.name)
        .catchThrow(new Error('Failed to parse package.json from the project'));
}

/**
 * Generates a zip package using a node buffer containing the necessary files
 *
 * @param {String} dir
 * @param {String[]} files
 * @param {String} name
 */
const createZip = curry((dir, files) => {
    const zip = new Zip();
    files.forEach(addToZip(zip, dir, _));
    return zip;
});

/**
 * Taking account the -o parameter can be used to specify the output directory,
 * let's deal with it
 *
 * @param {String} customPath
 * @param {String} filename
 * @return {String}
 */
function resolveOutputTarget(customPath, filename) {
    const realPath = path.resolve('.', customPath);

    const getPath = tryCatch(realPath => fs.lstatSync(realPath).isDirectory()
        ? path.join(realPath, filename)
        : realPath
    , identity);

    return getPath(realPath);
}

/**
 * Saves the zip file from buffer to the filesystem
 *
 * @param {String} dir
 * @param {Zip} zip
 * @param {String} name
 */
const saveZip = curry((dir, zip, name) => {
    const target = resolveOutputTarget(dir, `${name}.rung`);

    return new Promise((resolve, reject) => {
        zip.generateNodeStream({ type: 'nodebuffer', streamFiles: true })
            .pipe(fs.createWriteStream(target))
            .on('error', reject)
            .on('finish', ~resolve(target));
    });
});

/**
 * Appends a file or folder to the zip buffer
 *
 * @param {Zip} zip
 * @param {String} dir
 * @param {String} filename
 */
function addToZip(zip, dir, filename) {
    return zip.file(filename, fs.readFileSync(path.join(dir, filename)), defaultFileOptions);
}

/**
 * Precompiles an extension and generates a .rung package
 *
 * @param {Object} args
 */
export default function build(args) {
    const dir = path.resolve('.', args._[1] || '');

    return fs.readdirAsync(dir)
        .then(filterFiles)
        .then(linkFiles)
        .then(precompile)
        .then(createZip(dir))
        .then(zip => all([zip, getProjectName(dir)]))
        .spread(saveZip(args.output || '.'))
        .tap(~emitSuccess('Rung extension compilation'));
}
<|endoftext|>


<commit_before>/* global describe, it, xit, console */
/* jslint node: true, esnext: true */

"use strict";

const chai = require('chai');
const assert = chai.assert;
const expect = chai.expect;
const should = chai.should();

const sc = require('../lib/scopeReporter.js');

const scopes = {
  'file': {
    properties: {
      name: {}
    },
    format: "file: ${name}"
  },
  'line': {
    parent: 'file',
    properties: {
      number: {}
    },
    format: "${file.name}/${number}"
  },
  'function': {
    properties: {
      name: {}
    },
    format: "function: ${name}"
  }
};

describe('reporter', function () {
  describe('create', function () {
    const reporter = sc.createReporter(scopes);

    it('scope stack empty', function () {
      assert.lengthOf(reporter.scopeStack, 0);
      assert.isUndefined(reporter.currentScope);
    });

    it('common scope definitions present', function () {
      assert.equal(reporter.scopeDefinitions.error.name, 'error');
      assert.equal(reporter.scopeDefinitions.exception.name, 'exception');
    });

    it('scope definitions present', function () {
      assert.equal(reporter.scopeDefinitions.file.name, 'file');
    });
  });

  describe('add scope definitions', function () {
    const reporter = sc.createReporter(scopes);

    it('scope definitions present', function () {
      reporter.addScopeDefinitions({
        'newScope': {
          properties: {
            "newScopeProp": {}
          },
          format: ""
        }
      });
      assert.equal(reporter.scopeDefinitions.newScope.name, 'newScope');
    });
  });

  describe('enter/leave scopes', function () {
    const reporter = sc.createReporter(scopes);

    it('default undefined scope', function () {
      assert.isUndefined(reporter.currentScope);
    });

    it('enter scope single scalar', function () {
      const reporter = sc.createReporter(scopes);
      const newScope = reporter.enterScope('file', 'aFile');
      assert.equal(reporter.currentScope, newScope);
      assert.lengthOf(reporter.scopeStack, 1);
      assert.equal(reporter.currentScope.values.name, 'aFile');
      assert.equal(reporter.scope('file'), reporter.currentScope);
    });

    it('enter scope', function () {
      const newScope = reporter.enterScope('file', {
        name: 'aFile'
      });
      assert.equal(reporter.currentScope, newScope);
      assert.lengthOf(reporter.scopeStack, 1);
      assert.equal(reporter.currentScope.values.name, 'aFile');
      assert.equal(reporter.scope('file'), reporter.currentScope);
    });

    it('leave scope again', function () {
      const oldScope = reporter.leaveScope();
      assert.lengthOf(reporter.scopeStack, 0);
      assert.isUndefined(reporter.currentScope);
      assert.equal(oldScope.name, 'file');
    });
  });

  describe('find scopes', function () {
    const reporter = sc.createReporter(scopes);
    reporter.enterScope('file', 'aFile');

    it('with name', function () {
      assert.equal(reporter.scope('file').name, 'file');
    });

    it('with invalid name', function () {
      assert.isUndefined(reporter.scope('no_present'));
    });

  });

  describe('enter/leave scopes guarded', function () {
    it('leave scope again', function () {
      const reporter = sc.createReporter(scopes);
      reporter.enterScope('file', {
        name: 'aFile'
      });
      reporter.leaveScope('file');
      assert.lengthOf(reporter.scopeStack, 0);
      assert.isUndefined(reporter.currentScope);
    });

    it('leave wrong scope should throw', function () {
      const reporter = sc.createReporter(scopes);
      reporter.enterScope('file', {
        name: 'aFile'
      });
      try {
        reporter.leaveScope('other');
        assert.lengthOf(reporter.scopeStack, 1);
      } catch (e) {
        assert.equal(e, 'Error: Leaving scope: expected to be in other but was in file scope');
      }
    });
  });

  describe('toString toJSON', function () {
    const reporter = sc.createReporter(scopes);
    reporter.enterScope('file', 'aFile');
    reporter.enterScope('error', 'something went wrong');
    assert.include(reporter.toString(), 'aFile');

    //console.log(JSON.stringify(reporter.toJSON()));

    assert.deepEqual(reporter.toJSON(), {
      "scopes": [{
        "name": "file",
        "properties": {
          "name": "aFile"
        }
      }, {
        "name": "error",
        "properties": {
          "error": "something went wrong"
        }
      }]
    });
  });

  describe('report', function () {
    describe('console adaptor', function () {
      const myConsole = {
        log(message) {
          assert.include(message, 'some error');
          assert.include(message, 'aFile');
        }
      };

      function reporterWithAssertions(severity) {
        return sc.createReporter(scopes, sc.createConsoleAdapter(myConsole));
      }

      it('trace', function () {
        const reporter = reporterWithAssertions('trace');
        reporter.trace('some error', 'file', 'aFile');
      });
    });

    describe('logging adaptor', function () {
      function reporterWithAssertions(severity) {
        function myAsserter(severity) {
          return function (message) {
            assert.include(message, 'some error');
            assert.include(message, 'aFile');
          };
        }

        const logger = {
          trace: myAsserter('trace'),
          debug: myAsserter('debug'),
          info: myAsserter('info'),
          warn: myAsserter('warn'),
          error: myAsserter('error'),
          fatal: myAsserter('fatal')
        };
        return sc.createReporter(scopes, sc.createLoggingAdapter(logger));
      }

      it('trace', function () {
        const reporter = reporterWithAssertions('trace');
        reporter.trace('some error', 'file', 'aFile');
      });
      it('debug', function () {
        const reporter = reporterWithAssertions('debug');
        reporter.debug('some error', 'file', 'aFile');
      });
      it('info', function () {
        const reporter = reporterWithAssertions('info');
        reporter.info('some error', 'file', 'aFile');
      });
      it('warn', function () {
        const reporter = reporterWithAssertions('warn');
        reporter.warn('some error', 'file', 'aFile');
      });
      it('error', function () {
        const reporter = reporterWithAssertions('error');
        reporter.error('some error', 'file', 'aFile');
      });
      it('fatal', function () {
        const reporter = reporterWithAssertions('fatal');
        reporter.fatal('some error', 'file', 'aFile');
      });
    });

    describe('internal model', function () {
      function reporterWithAssertions(severity) {
        return sc.createReporter(scopes, function (reporter) {
          assert.lengthOf(reporter.scopeStack, 2);
          assert.equal(reporter.scope('severity').values.severity, severity);
          assert.equal(reporter.scope('severity').values.message, 'some error');
          assert.equal(reporter.scope('file').values.name, 'aFile');
        });
      }

      if('exception', function () {
        const reporter = reporterWithAssertions('exception');
        reporter.enterScope('file', 'aFile');
        reporter.exception(new Error('some exception'));
        });

      it('trace', function () {
        const reporter = reporterWithAssertions('trace');
        reporter.trace('some error', 'file', 'aFile');
      });

      it('debug', function () {
        const reporter = reporterWithAssertions('debug');
        reporter.debug('some error', 'file', 'aFile');
      });

      it('info', function () {
        const reporter = reporterWithAssertions('info');
        reporter.info('some error', 'file', 'aFile');
      });

      it('warn', function () {
        const reporter = reporterWithAssertions('warn');
        reporter.warn('some error', 'file', 'aFile');
      });

      it('error', function () {
        const reporter = reporterWithAssertions('error');
        reporter.error('some error', 'file', 'aFile');
      });

      it('fatal', function () {
        const reporter = reporterWithAssertions('fatal');
        reporter.fatal('some error', 'file', 'aFile');
      });
    });
  });
});
<commit_msg>style(test): js format only<commit_after>/* global describe, it, xit, console */
/* jslint node: true, esnext: true */

"use strict";

const chai = require('chai');
const assert = chai.assert;
const expect = chai.expect;
const should = chai.should();

const sc = require('../lib/scopeReporter.js');

const scopes = {
  'file': {
    properties: {
      name: {}
    },
    format: "file: ${name}"
  },
  'line': {
    parent: 'file',
    properties: {
      number: {}
    },
    format: "${file.name}/${number}"
  },
  'function': {
    properties: {
      name: {}
    },
    format: "function: ${name}"
  }
};

describe('reporter', function () {
  describe('create', function () {
    const reporter = sc.createReporter(scopes);

    it('scope stack empty', function () {
      assert.lengthOf(reporter.scopeStack, 0);
      assert.isUndefined(reporter.currentScope);
    });

    it('common scope definitions present', function () {
      assert.equal(reporter.scopeDefinitions.error.name, 'error');
      assert.equal(reporter.scopeDefinitions.exception.name, 'exception');
    });

    it('scope definitions present', function () {
      assert.equal(reporter.scopeDefinitions.file.name, 'file');
    });
  });

  describe('add scope definitions', function () {
    const reporter = sc.createReporter(scopes);

    it('scope definitions present', function () {
      reporter.addScopeDefinitions({
        'newScope': {
          properties: {
            "newScopeProp": {}
          },
          format: ""
        }
      });
      assert.equal(reporter.scopeDefinitions.newScope.name, 'newScope');
    });
  });

  describe('enter/leave scopes', function () {
    const reporter = sc.createReporter(scopes);

    it('default undefined scope', function () {
      assert.isUndefined(reporter.currentScope);
    });

    it('enter scope single scalar', function () {
      const reporter = sc.createReporter(scopes);
      const newScope = reporter.enterScope('file', 'aFile');
      assert.equal(reporter.currentScope, newScope);
      assert.lengthOf(reporter.scopeStack, 1);
      assert.equal(reporter.currentScope.values.name, 'aFile');
      assert.equal(reporter.scope('file'), reporter.currentScope);
    });

    it('enter scope', function () {
      const newScope = reporter.enterScope('file', {
        name: 'aFile'
      });
      assert.equal(reporter.currentScope, newScope);
      assert.lengthOf(reporter.scopeStack, 1);
      assert.equal(reporter.currentScope.values.name, 'aFile');
      assert.equal(reporter.scope('file'), reporter.currentScope);
    });

    it('leave scope again', function () {
      const oldScope = reporter.leaveScope();
      assert.lengthOf(reporter.scopeStack, 0);
      assert.isUndefined(reporter.currentScope);
      assert.equal(oldScope.name, 'file');
    });
  });

  describe('find scopes', function () {
    const reporter = sc.createReporter(scopes);
    reporter.enterScope('file', 'aFile');

    it('with name', function () {
      assert.equal(reporter.scope('file').name, 'file');
    });

    it('with invalid name', function () {
      assert.isUndefined(reporter.scope('no_present'));
    });

  });

  describe('enter/leave scopes guarded', function () {
    it('leave scope again', function () {
      const reporter = sc.createReporter(scopes);
      reporter.enterScope('file', {
        name: 'aFile'
      });
      reporter.leaveScope('file');
      assert.lengthOf(reporter.scopeStack, 0);
      assert.isUndefined(reporter.currentScope);
    });

    it('leave wrong scope should throw', function () {
      const reporter = sc.createReporter(scopes);
      reporter.enterScope('file', {
        name: 'aFile'
      });
      try {
        reporter.leaveScope('other');
        assert.lengthOf(reporter.scopeStack, 1);
      } catch (e) {
        assert.equal(e, 'Error: Leaving scope: expected to be in other but was in file scope');
      }
    });
  });

  describe('toString toJSON', function () {
    const reporter = sc.createReporter(scopes);
    reporter.enterScope('file', 'aFile');
    reporter.enterScope('error', 'something went wrong');
    assert.include(reporter.toString(), 'aFile');

    //console.log(JSON.stringify(reporter.toJSON()));

    assert.deepEqual(reporter.toJSON(), {
      "scopes": [{
        "name": "file",
        "properties": {
          "name": "aFile"
        }
      }, {
        "name": "error",
        "properties": {
          "error": "something went wrong"
        }
      }]
    });
  });

  describe('report', function () {
    describe('console adaptor', function () {
      const myConsole = {
        log(message) {
          assert.include(message, 'some error');
          assert.include(message, 'aFile');
        }
      };

      function reporterWithAssertions(severity) {
        return sc.createReporter(scopes, sc.createConsoleAdapter(myConsole));
      }

      it('trace', function () {
        const reporter = reporterWithAssertions('trace');
        reporter.trace('some error', 'file', 'aFile');
      });
    });

    describe('logging adaptor', function () {
      function reporterWithAssertions(severity) {
        function myAsserter(severity) {
          return function (message) {
            assert.include(message, 'some error');
            assert.include(message, 'aFile');
          };
        }

        const logger = {
          trace: myAsserter('trace'),
          debug: myAsserter('debug'),
          info: myAsserter('info'),
          warn: myAsserter('warn'),
          error: myAsserter('error'),
          fatal: myAsserter('fatal')
        };
        return sc.createReporter(scopes, sc.createLoggingAdapter(logger));
      }

      it('trace', function () {
        const reporter = reporterWithAssertions('trace');
        reporter.trace('some error', 'file', 'aFile');
      });
      it('debug', function () {
        const reporter = reporterWithAssertions('debug');
        reporter.debug('some error', 'file', 'aFile');
      });
      it('info', function () {
        const reporter = reporterWithAssertions('info');
        reporter.info('some error', 'file', 'aFile');
      });
      it('warn', function () {
        const reporter = reporterWithAssertions('warn');
        reporter.warn('some error', 'file', 'aFile');
      });
      it('error', function () {
        const reporter = reporterWithAssertions('error');
        reporter.error('some error', 'file', 'aFile');
      });
      it('fatal', function () {
        const reporter = reporterWithAssertions('fatal');
        reporter.fatal('some error', 'file', 'aFile');
      });
    });

    describe('internal model', function () {
      function reporterWithAssertions(severity) {
        return sc.createReporter(scopes, function (reporter) {
          assert.lengthOf(reporter.scopeStack, 2);
          assert.equal(reporter.scope('severity').values.severity, severity);
          assert.equal(reporter.scope('severity').values.message, 'some error');
          assert.equal(reporter.scope('file').values.name, 'aFile');
        });
      }

      if ('exception', function () {
          const reporter = reporterWithAssertions('exception');
          reporter.enterScope('file', 'aFile');
          reporter.exception(new Error('some exception'));
        });

      it('trace', function () {
        const reporter = reporterWithAssertions('trace');
        reporter.trace('some error', 'file', 'aFile');
      });

      it('debug', function () {
        const reporter = reporterWithAssertions('debug');
        reporter.debug('some error', 'file', 'aFile');
      });

      it('info', function () {
        const reporter = reporterWithAssertions('info');
        reporter.info('some error', 'file', 'aFile');
      });

      it('warn', function () {
        const reporter = reporterWithAssertions('warn');
        reporter.warn('some error', 'file', 'aFile');
      });

      it('error', function () {
        const reporter = reporterWithAssertions('error');
        reporter.error('some error', 'file', 'aFile');
      });

      it('fatal', function () {
        const reporter = reporterWithAssertions('fatal');
        reporter.fatal('some error', 'file', 'aFile');
      });
    });
  });
});
<|endoftext|>