<commit_before>/* global WebSocket */

// Custom object wrapping logic to websocket and exposing a subscription
// interface
let cozySocket

// Important, must match the spec,
// see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
const WEBSOCKET_STATE = {
  OPEN: 1
}

const KEEPALIVE = {
  INTERVAL: 30000,
  METHOD_NAME: 'ping'
}

// Send a subscribe message for the given doctype trough the given websocket, but
// only if it is in a ready state. If not, retry a few milliseconds later.
function subscribeWhenReady (doctype, socket) {
  if (socket.readyState === WEBSOCKET_STATE.OPEN) {
    try {
      socket.send(JSON.stringify({
        method: 'SUBSCRIBE',
        payload: {
          type: doctype
        }
      }))
    } catch (error) {
      console.warn && console.warn(`Cannot subscribe to doctype ${doctype}: ${error.message}`)
      throw error
    }
  } else {
    setTimeout(() => {
      subscribeWhenReady(doctype, socket)
    }, 10)
  }
}

function getWebsocketProtocol () {
  return window.location.protocol === 'http:' ? 'ws' : 'wss'
}

function keepAlive (socket, interval, message) {
  const keepAliveInterval = setInterval(() => {
    if (socket.readyState === WEBSOCKET_STATE.OPEN) {
      socket.send(message)
    } else {
      clearInterval(keepAliveInterval)
    }
  }, interval)
}

async function connectWebSocket (cozy, onmessage, onclose) {
  return new Promise((resolve, reject) => {
    const protocol = getWebsocketProtocol()
    const socket = new WebSocket(`${protocol}:${cozy._url}/realtime/`, 'io.cozy.websocket')

    socket.onmessage = onmessage
    socket.onclose = onclose

    socket.onopen = (event) => {
      try {
        socket.send(JSON.stringify({
          method: 'AUTH',
          payload: cozy._token.token
        }))
      } catch (error) {
        return reject(error)
      }

      resolve(keepAlive(socket, KEEPALIVE.INTERVAL, `{"method":"${KEEPALIVE.METHOD_NAME}"}`))
    }
  })
}

function getCozySocket (cozy) {
  return new Promise(async (resolve, reject) => {
    if (cozySocket) return resolve(cozySocket)

    const listeners = {}

    let socket

    cozySocket = {
      subscribe: (doctype, event, listener) => {
        if (typeof listener !== 'function') throw new Error('Realtime event listener must be a function')

        if (!listeners[doctype]) {
          listeners[doctype] = {}
          subscribeWhenReady(doctype, socket)
        }

        listeners[doctype][event] = (listeners[doctype][event] || []).concat([listener])
      },
      unsubscribe: (doctype, event, listener) => {
        if (listeners[doctype] && listeners[doctype][event] && listeners[doctype][event].includes(listener)) {
          listeners[doctype][event] = listeners[doctype][event].filter(l => l !== listener)
        }
      }
    }

    const onSocketMessage = (event) => {
      const data = JSON.parse(event.data)
      const eventType = data.event.toLowerCase()
      const payload = data.payload

      if (eventType === 'error') {
        const isPingError = data.payload && data.payload.source && data.payload.source.method === KEEPALIVE.METHOD_NAME
        if (isPingError) return

        const realtimeError = new Error(payload.title)
        const errorFields = ['status', 'code', 'source']
        errorFields.forEach(property => {
          realtimeError[property] = payload[property]
        })

        throw realtimeError
      }

      if (listeners[payload.type] && listeners[payload.type][eventType]) {
        listeners[payload.type][eventType].forEach(listener => {
          listener(payload.doc)
        })
      }
    }

    const onSocketClose = async (event) => {
      if (!event.wasClean) {
        console.warn && console.warn(`WebSocket closed unexpectedly with code ${event.code} and ${event.reason ? `reason: '${event.reason}'` : 'no reason'}. Reconnecting.`)
        try {
          socket = await connectWebSocket(cozy, onSocketMessage, onSocketClose)
        } catch (error) {
          console.error && console.error(`Unable to reconnect to realtime. Error: ${error.message}`)
        }
      }
    }

    try {
      socket = await connectWebSocket(cozy, onSocketMessage, onSocketClose)
    } catch (error) {
      reject(error)
    }

    resolve(cozySocket)
  })
}

// Returns the Promise of a subscription to a given doctype and document
export function subscribe (cozy, doctype, doc, parse = doc => doc) {
  return subscribeAll(cozy, doctype, parse)
    .then(subscription => {
      // We will call the listener only for the given document, so let's curry it
      const docListenerCurried = (listener) => {
        return syncedDoc => {
          if (syncedDoc._id === doc._id) {
            listener(syncedDoc)
          }
        }
      }

      return {
        onCreate: (listener) => subscription.onCreate(docListenerCurried(listener)),
        onUpdate: (listener) => subscription.onUpdate(docListenerCurried(listener)),
        onDelete: (listener) => subscription.onDelete(docListenerCurried(listener)),
        unsubscribe: () => subscription.unsubscribe()
      }
    })
}

// Returns the Promise of a subscription to a given doctype (all documents)
export function subscribeAll (cozy, doctype, parse = doc => doc) {
  return getCozySocket(cozy)
    .then(cozySocket => {
      // Some document need to have specific parsing, for example, decoding
      // base64 encoded properties
      const parseCurried = (listener) => {
        return doc => {
          listener(parse(doc))
        }
      }

      let createListener, updateListener, deleteListener

      const subscription = {
        onCreate: (listener) => {
          createListener = parseCurried(listener)
          cozySocket.subscribe(doctype, 'created', createListener)
          return subscription
        },
        onUpdate: (listener) => {
          updateListener = parseCurried(listener)
          cozySocket.subscribe(doctype, 'updated', updateListener)
          return subscription
        },
        onDelete: (listener) => {
          deleteListener = parseCurried(listener)
          cozySocket.subscribe(doctype, 'deleted', deleteListener)
          return subscription
        },
        unsubscribe: () => {
          cozySocket.unsubscribe(doctype, 'created', createListener)
          cozySocket.unsubscribe(doctype, 'updated', updateListener)
          cozySocket.unsubscribe(doctype, 'deleted', deleteListener)
        }
      }

      return subscription
    })
}
<commit_msg>fix: moving socket callback affectation to avoid close/connect loop :pencil:<commit_after>/* global WebSocket */

// Custom object wrapping logic to websocket and exposing a subscription
// interface
let cozySocket

// Important, must match the spec,
// see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
const WEBSOCKET_STATE = {
  OPEN: 1
}

const KEEPALIVE = {
  INTERVAL: 30000,
  METHOD_NAME: 'ping'
}

// Send a subscribe message for the given doctype trough the given websocket, but
// only if it is in a ready state. If not, retry a few milliseconds later.
function subscribeWhenReady (doctype, socket) {
  if (socket.readyState === WEBSOCKET_STATE.OPEN) {
    try {
      socket.send(JSON.stringify({
        method: 'SUBSCRIBE',
        payload: {
          type: doctype
        }
      }))
    } catch (error) {
      console.warn && console.warn(`Cannot subscribe to doctype ${doctype}: ${error.message}`)
      throw error
    }
  } else {
    setTimeout(() => {
      subscribeWhenReady(doctype, socket)
    }, 10)
  }
}

function getWebsocketProtocol () {
  return window.location.protocol === 'http:' ? 'ws' : 'wss'
}

function keepAlive (socket, interval, message) {
  const keepAliveInterval = setInterval(() => {
    if (socket.readyState === WEBSOCKET_STATE.OPEN) {
      socket.send(message)
    } else {
      clearInterval(keepAliveInterval)
    }
  }, interval)
}

async function connectWebSocket (cozy, onmessage, onclose) {
  return new Promise((resolve, reject) => {
    const protocol = getWebsocketProtocol()
    const socket = new WebSocket(`${protocol}:${cozy._url}/realtime/`, 'io.cozy.websocket')

    socket.onopen = (event) => {
      try {
        socket.send(JSON.stringify({
          method: 'AUTH',
          payload: cozy._token.token
        }))
      } catch (error) {
        return reject(error)
      }

      socket.onmessage = onmessage
      socket.onclose = onclose

      resolve(keepAlive(socket, KEEPALIVE.INTERVAL, `{"method":"${KEEPALIVE.METHOD_NAME}"}`))
    }
  })
}

function getCozySocket (cozy) {
  return new Promise(async (resolve, reject) => {
    if (cozySocket) return resolve(cozySocket)

    const listeners = {}

    let socket

    cozySocket = {
      subscribe: (doctype, event, listener) => {
        if (typeof listener !== 'function') throw new Error('Realtime event listener must be a function')

        if (!listeners[doctype]) {
          listeners[doctype] = {}
          subscribeWhenReady(doctype, socket)
        }

        listeners[doctype][event] = (listeners[doctype][event] || []).concat([listener])
      },
      unsubscribe: (doctype, event, listener) => {
        if (listeners[doctype] && listeners[doctype][event] && listeners[doctype][event].includes(listener)) {
          listeners[doctype][event] = listeners[doctype][event].filter(l => l !== listener)
        }
      }
    }

    const onSocketMessage = (event) => {
      const data = JSON.parse(event.data)
      const eventType = data.event.toLowerCase()
      const payload = data.payload

      if (eventType === 'error') {
        const isPingError = data.payload && data.payload.source && data.payload.source.method === KEEPALIVE.METHOD_NAME
        if (isPingError) return

        const realtimeError = new Error(payload.title)
        const errorFields = ['status', 'code', 'source']
        errorFields.forEach(property => {
          realtimeError[property] = payload[property]
        })

        throw realtimeError
      }

      if (listeners[payload.type] && listeners[payload.type][eventType]) {
        listeners[payload.type][eventType].forEach(listener => {
          listener(payload.doc)
        })
      }
    }

    const onSocketClose = async (event) => {
      if (!event.wasClean) {
        console.warn && console.warn(`WebSocket closed unexpectedly with code ${event.code} and ${event.reason ? `reason: '${event.reason}'` : 'no reason'}. Reconnecting.`)
        try {
          socket = await connectWebSocket(cozy, onSocketMessage, onSocketClose)
        } catch (error) {
          console.error && console.error(`Unable to reconnect to realtime. Error: ${error.message}`)
        }
      }
    }

    try {
      socket = await connectWebSocket(cozy, onSocketMessage, onSocketClose)
    } catch (error) {
      reject(error)
    }

    resolve(cozySocket)
  })
}

// Returns the Promise of a subscription to a given doctype and document
export function subscribe (cozy, doctype, doc, parse = doc => doc) {
  return subscribeAll(cozy, doctype, parse)
    .then(subscription => {
      // We will call the listener only for the given document, so let's curry it
      const docListenerCurried = (listener) => {
        return syncedDoc => {
          if (syncedDoc._id === doc._id) {
            listener(syncedDoc)
          }
        }
      }

      return {
        onCreate: (listener) => subscription.onCreate(docListenerCurried(listener)),
        onUpdate: (listener) => subscription.onUpdate(docListenerCurried(listener)),
        onDelete: (listener) => subscription.onDelete(docListenerCurried(listener)),
        unsubscribe: () => subscription.unsubscribe()
      }
    })
}

// Returns the Promise of a subscription to a given doctype (all documents)
export function subscribeAll (cozy, doctype, parse = doc => doc) {
  return getCozySocket(cozy)
    .then(cozySocket => {
      // Some document need to have specific parsing, for example, decoding
      // base64 encoded properties
      const parseCurried = (listener) => {
        return doc => {
          listener(parse(doc))
        }
      }

      let createListener, updateListener, deleteListener

      const subscription = {
        onCreate: (listener) => {
          createListener = parseCurried(listener)
          cozySocket.subscribe(doctype, 'created', createListener)
          return subscription
        },
        onUpdate: (listener) => {
          updateListener = parseCurried(listener)
          cozySocket.subscribe(doctype, 'updated', updateListener)
          return subscription
        },
        onDelete: (listener) => {
          deleteListener = parseCurried(listener)
          cozySocket.subscribe(doctype, 'deleted', deleteListener)
          return subscription
        },
        unsubscribe: () => {
          cozySocket.unsubscribe(doctype, 'created', createListener)
          cozySocket.unsubscribe(doctype, 'updated', updateListener)
          cozySocket.unsubscribe(doctype, 'deleted', deleteListener)
        }
      }

      return subscription
    })
}
<|endoftext|>


<commit_before>import { L10nError } from './errors';

const KNOWN_MACROS = ['PLURAL'];
const MAX_PLACEABLE_LENGTH = 2500;

// Unicode bidi isolation characters
const FSI = '\u2068';
const PDI = '\u2069';

// Helper function for inserting a placeable value into a string

function stringify(res, value) {
  if (typeof value === 'string') {
    return FSI + value + PDI;
  }

  if (typeof value === 'number' && !isNaN(value)) {
    const formatter = res.ctx._getNumberFormatter(res.lang);
    return formatter.format(value);
  }

  if (typeof value === 'function') {
    return FSI + '{function}' + PDI;
  }
}

// Helper functions for picking the right member from an array of traits or
// variants.

function chooseTrait(entity, name) {
  for (let trait of entity.traits) {

    if (name === trait.key || (!name && trait.default)) {
      return trait;
    }
  }
}

function chooseVariant(placeable, expr) {
  if (typeof expr === 'function') {
    for (let variant of placeable.variants) {
      if (expr(variant.key)) {
        return variant;
      }
    }
  } else {
    for (let variant of placeable.variants) {
      if (expr === variant.key) {
        return variant;
      }
    }
  }

  for (let variant of placeable.variants) {
    if (variant.default) {
      return variant;
    }
  }

  throw new L10nError('No default variant found');
}

// resolve* functions can throw and return a single value

function resolveEntity(res, expr) {
  const id = expr.name;
  const entity = res.ctx._getEntity(res.lang, id);

  if (!entity) {
    throw new L10nError('Unknown reference: ' + id);
  }

  if (res.dirty.has(entity)) {
    throw new L10nError('Cyclic reference: ' + id);
  }

  const value = entity.value !== null ?
    entity.value :
    chooseTrait(entity).value;

  return resolveValue(res, value);
}

function resolveVariable(res, expr) {
  const id = expr.id.name;
  const args = res.args;

  if (args && args.hasOwnProperty(id)) {
    return args[id];
  }

  throw new L10nError('Unknown reference: ' + id);
}

function resolveLiteral(res, expr) {
  return expr.value;
}

function resolveCall(res, expr) {
  const id = expr.callee.name;

  if (KNOWN_MACROS.indexOf(id) === -1) {
    throw new L10nError('Unknown reference: ' + id);
  }

  const callee = res.ctx._getMacro(res.lang, id);
  return callee(
    ...expr.args.map(
      argExpr => resolveExpression(res, argExpr)
    )
  );
}

function resolveTrait(res, expr) {
  const id = expr.idref.name;
  const key = expr.keyword;
  const entity = res.ctx._getEntity(res.lang, id);

  if (!entity) {
    throw new L10nError('Unknown entity: ' + id);
  }

  const trait = chooseTrait(entity, key);

  if (!trait) {
    throw new L10nError('Unknown trait: ' + key);
  }

  return resolveValue(res, trait.value);
}

function resolveExpression(res, expr) {
  // XXX switch?
  if (expr.type === 'Identifier') {
    return resolveEntity(res, expr);
  } else if (expr.type === 'Variable') {
    return resolveVariable(res, expr);
  } else if (expr.type === 'Number') {
    return resolveLiteral(res, expr);
  } else if (expr.type === 'CallExpression') {
    return resolveCall(res, expr);
  } else if (expr.type === 'MemberExpression') {
    return resolveTrait(res, expr);
  } else {
    throw new L10nError('Unknown placeable type');
  }
}

function resolvePlaceable(res, placeable) {
  const expr = resolveExpression(res, placeable.expression);

  if (!placeable.variants) {
    return expr;
  }

  const variant = chooseVariant(placeable, expr);
  return resolveValue(res, variant.value);
}

function resolveValue(res, value) {
  const [errs, str] = formatValue(res, value);

  if (errs.length) {
    throw new L10nError('Broken value.');
  }

  return str;
}

// formatValue collects any errors and return them as the first element of
// the return tuple: [errors, value]

function formatValue(res, value) {
  return value.elements.reduce(([errs, seq], elem) => {
    if (typeof elem === 'string') {
      return [errs, seq + elem];
    } else if (elem.type === 'Placeable') {
      try {
        const placeable = resolvePlaceable(res, elem);
        if (placeable.length >= MAX_PLACEABLE_LENGTH) {
          throw new L10nError(
            'Too many characters in placeable (' + placeable.length +
            ', max allowed is ' + MAX_PLACEABLE_LENGTH + ')'
          );
        }
        return [errs, seq + stringify(res, placeable)];
      } catch(e) {
        return [[...errs, e], seq + stringify(res, '{}')];
      }
    } else {
      return [
        [...errs, new L10nError('Unresolvable value')],
        seq + stringify(res, '{}')
      ];
    }
  }, [[], '']);
}

export function format(ctx, lang, args, entity) {
  const res = {
    ctx,
    lang,
    args,
    errors: [],
    dirty: new WeakSet([entity])
  };

  const value = entity.value !== null ?
    entity.value :
    chooseTrait(entity).value;

  return formatValue(res, value);
}
<commit_msg>move MAX_PLACEABLE_LENGTH check into resolvePlaceable<commit_after>import { L10nError } from './errors';

const KNOWN_MACROS = ['PLURAL'];
const MAX_PLACEABLE_LENGTH = 2500;

// Unicode bidi isolation characters
const FSI = '\u2068';
const PDI = '\u2069';

// Helper function for inserting a placeable value into a string

function stringify(res, value) {
  if (typeof value === 'string') {
    return FSI + value + PDI;
  }

  if (typeof value === 'number' && !isNaN(value)) {
    const formatter = res.ctx._getNumberFormatter(res.lang);
    return formatter.format(value);
  }

  if (typeof value === 'function') {
    return FSI + '{function}' + PDI;
  }
}

// Helper functions for picking the right member from an array of traits or
// variants.

function chooseTrait(entity, name) {
  for (let trait of entity.traits) {

    if (name === trait.key || (!name && trait.default)) {
      return trait;
    }
  }
}

function chooseVariant(variants, expr) {
  if (typeof expr === 'function') {
    for (let variant of variants) {
      if (expr(variant.key)) {
        return variant;
      }
    }
  } else {
    for (let variant of variants) {
      if (expr === variant.key) {
        return variant;
      }
    }
  }

  for (let variant of variants) {
    if (variant.default) {
      return variant;
    }
  }

  throw new L10nError('No default variant found');
}

// resolve* functions can throw and return a single value

function resolveEntity(res, expr) {
  const id = expr.name;
  const entity = res.ctx._getEntity(res.lang, id);

  if (!entity) {
    throw new L10nError('Unknown reference: ' + id);
  }

  if (res.dirty.has(entity)) {
    throw new L10nError('Cyclic reference: ' + id);
  }

  const value = entity.value !== null ?
    entity.value :
    chooseTrait(entity).value;

  return resolveValue(res, value);
}

function resolveVariable(res, expr) {
  const id = expr.id.name;
  const args = res.args;

  if (args && args.hasOwnProperty(id)) {
    return args[id];
  }

  throw new L10nError('Unknown reference: ' + id);
}

function resolveLiteral(res, expr) {
  return expr.value;
}

function resolveCall(res, expr) {
  const id = expr.callee.name;

  if (KNOWN_MACROS.indexOf(id) === -1) {
    throw new L10nError('Unknown reference: ' + id);
  }

  const callee = res.ctx._getMacro(res.lang, id);
  return callee(
    ...expr.args.map(
      argExpr => resolveExpression(res, argExpr)
    )
  );
}

function resolveTrait(res, expr) {
  const id = expr.idref.name;
  const key = expr.keyword;
  const entity = res.ctx._getEntity(res.lang, id);

  if (!entity) {
    throw new L10nError('Unknown entity: ' + id);
  }

  const trait = chooseTrait(entity, key);

  if (!trait) {
    throw new L10nError('Unknown trait: ' + key);
  }

  return resolveValue(res, trait.value);
}

function resolveExpression(res, expr) {
  // XXX switch?
  if (expr.type === 'Identifier') {
    return resolveEntity(res, expr);
  } else if (expr.type === 'Variable') {
    return resolveVariable(res, expr);
  } else if (expr.type === 'Number') {
    return resolveLiteral(res, expr);
  } else if (expr.type === 'CallExpression') {
    return resolveCall(res, expr);
  } else if (expr.type === 'MemberExpression') {
    return resolveTrait(res, expr);
  } else {
    throw new L10nError('Unknown placeable type');
  }
}

function resolvePlaceable(res, placeable) {
  const expr = resolveExpression(res, placeable.expression);

  const value = placeable.variants === null ?
    expr :
    resolveValue(res, chooseVariant(placeable.variants, expr).value);

  if (value.length >= MAX_PLACEABLE_LENGTH) {
    throw new L10nError(
      'Too many characters in placeable (' + value.length +
        ', max allowed is ' + MAX_PLACEABLE_LENGTH + ')'
    );
  }

  return value;
}

function resolveValue(res, value) {
  const [errs, str] = formatValue(res, value);

  if (errs.length) {
    throw new L10nError('Broken value.');
  }

  return str;
}

// formatValue collects any errors and return them as the first element of
// the return tuple: [errors, value]

function formatValue(res, value) {
  return value.elements.reduce(([errs, seq], elem) => {
    if (typeof elem === 'string') {
      return [errs, seq + elem];
    } else if (elem.type === 'Placeable') {
      try {
        return [errs, seq + stringify(res, resolvePlaceable(res, elem))];
      } catch(e) {
        return [[...errs, e], seq + stringify(res, '{}')];
      }
    } else {
      return [
        [...errs, new L10nError('Unresolvable value')],
        seq + stringify(res, '{}')
      ];
    }
  }, [[], '']);
}

export function format(ctx, lang, args, entity) {
  const res = {
    ctx,
    lang,
    args,
    errors: [],
    dirty: new WeakSet([entity])
  };

  const value = entity.value !== null ?
    entity.value :
    chooseTrait(entity).value;

  return formatValue(res, value);
}
<|endoftext|>


<commit_before>import {time} from 'd3';

let formatTime = function (d) {
 return time.format('%b, %Y')(new Date(d));
}

const stateIds =
						[
							{ value: 1, label: 'AK' },
							{ value: 30, label: 'NY' },
							{ value: 2, label: 'AZ' },
							{ value: 31, label: 'NC' },
							{ value: 3, label: 'AR' },
							{ value: 32, label: 'ND' },
							{ value: 4, label: 'CA' },
							{ value: 33, label: 'OH' },
							{ value: 5, label: 'CO' },
							{ value: 34, label: 'OK' },
							{ value: 6, label: 'CT' },
							{ value: 35, label: 'OR' },
							{ value: 7, label: 'DE' },
							{ value: 36, label: 'PA' },
							{ value: 8, label: 'FL' },
							{ value: 7, label: 'RI' },
							{ value: 9, label: 'GA' },
							{ value: 38, label: 'SC' },
							{ value: 10, label: 'ID' },
							{ value: 39, label: 'SD' },
							{ value: 11, label: 'IL' },
							{ value: 40, label: 'TN' },
							{ value: 12, label: 'IA' },
							{ value: 41, label: 'TX' },
							{ value: 13, label: 'IA' },
							{ value: 42, label: 'UT' },
							{ value: 14, label: 'KS' },
							{ value: 43, label: 'VT' },
							{ value: 15, label: 'KT' },
							{ value: 44, label: 'VA' },
							{ value: 16, label: 'LA' },
							{ value: 45, label: 'WA' },
							{ value: 17, label: 'ME' },
							{ value: 46, label: 'WV' },
							{ value: 18, label: 'MD' },
							{ value: 47, label: 'WI' },
							{ value: 19, label: 'MA' },
							{ value: 48, label: 'WY' },
							{ value: 20, label: 'MI' },
							{ value: 21, label: 'MN' },
							{ value: 22, label: 'MI' },
							{ value: 103, label: 'Central Region' },
							{ value: 23, label: 'MO' },
							{ value: 24, label: 'MT' },
							{ value: 25, label: 'NE' },
							{ value: 106, label: 'South Region' },
							{ value: 26, label: 'NV' },
							{ value: 27, label: 'NH' },
							{ value: 28, label: 'NJ' },
							{ value: 29, label: 'NM' },
              { value: 50, label: 'USA?'}
						]

var _settings = {
//export var settings = {
  title: 'React-Dash Demo -- Climate Indices by Year',
          ://github.com/NuCivic/react_dashboard/issueslassName: 'col-md-6',
  queries: {},
	// we will add this to globalData in app.js
  // and use these labels in our customDatahandlers
  climate_vars: {
    PCP: 'Precipitation Index',
    TAVG: 'Temperature Index',
    TMIN: 'Minimum Temperature Index',
    TMAX: 'Maximum Temperature Index',
    PDSI: 'Palmer Drought Severity Index',
    PHDI: 'Palmer Hydrological Drought Index',
    ZNDX: 'Palmer Z-Index',
    PMDI: 'Modified Palmer Drought Severity Index',
    CDD: 'Cooling Degree Days',
    HDD: 'Heating Degree Days',
    SPnn: 'Standard Precipitation Index'
	},

  // if defined at the top level, fetchData will populate
  // the dashboard with globalData
  fetchData: {
    type: 'backend',
    backend: 'csv',
    url: 'data/climate_indices.csv'
  },
  // if applied at the top level, datahandlers will filter global data
  dataHandlers: ['filterData'],

  components: [

    // region top
    {
      type: 'Region',
      cardStyle: 'content',
      header: "Climate Metrics",
      className: 'region region-top row',
      children: [
        {
          type: 'Autocomplete',
          cid: 'a1',
          field: 'year',
          placeholder: 'Select year...',
          dataHandlers: [{name: 'common.getEventReturn'}],
          initVal: '2015',
          fetch: true,
          options: [
            { label: '2010', value: '2010' },
            { label: '2011', value: '2011' },
            { label: '2012', value: '2012' },
            { label: '2013', value: '2013' },
            { label: '2014', value: '2014' },
            { label: '2015', value: '2015' },
          ]
        },
      ]
    },
    {
      type: 'Region',
      className: 'region-metrics region row',
      children: [
        {
          type: 'Metric',
          cardStyle: 'metric',
          iconClass: 'fa fa-level-up',
          className: 'col-md-4',
          caption: 'Maximum Temp.',
          dataHandlers: ['getMaxTemp']
        },
        {
          type: 'Metric',
          cardStyle: 'metric',
          iconClass: 'fa fa-level-down',
          className: 'col-md-4',
          background: '#53ACC9',
          caption: 'Minimum Temp.',
          dataHandlers: ['getMinTemp']
        },
        {
          type: 'Metric',
          cardStyle: 'metric',
          iconClass: 'fa fa-fire',
          className: 'col-md-4',
          caption: 'Average Temp/',
          background: '#C97053',
          dataHandlers: ['getAvgTemp']
        }
      ]
    },

    // region choropleth
    {
      type: 'Region',
      className: 'region-choropleth',
      children: [
        // choropleth
        {
          type: 'Choropleth',
          cid: 'choro1',
          cardStyle: 'map',
          header: 'Palmer Hydrological Drought Index',
          format: 'topojson',
          dataHandlers: [
            {
              name: 'getMapData',
              stateArray: stateIds
            }
          ],
          dataKeyField: 'name',
          dataValueField: 'PHDI',
          geometryKeyField: 'name',
          geometry: 'data/map/usa.json', // topojson or geojson
          projection: 'albersUsa', // https://github.com/d3/d3/wiki/Geo-Projections
          scaleDenominator: .8,
          borderColor: 'white',
          noDataColor: 'red',
          topologyObject: 'usa',
          startColor: 'red',
          endColor: 'yellow',
          dataClassification: 'equidistant',
          legend: {
            classesCount: 5,
            palleteKey: 'GnBu',
            pallete: ['#ff3333','#ff4d4d','#ff6666','#ff8080','#ff9999','#ffb3b3','#ffcccc'],
          },
        },
      ]
    },
    {
      type: 'Region',
      className: 'region region-lower row',
      children: [
        {
          type: 'Markup',
          content: '
Hydrological drought is described as a sustained and regionally extensive occurrence of below average natural water availability (Tallaksen and van Lanen, 2004). Hydrological drought as period of time below the average water content in streams, reservoirs, groundwater aquifers, lakes and soils. The period is associated effects of precipitation (including snowfall) shortfall on surface and subsurface water supply, rather than with direct shortfall in precipitation (Yevjevich et al., 1977). Hydrological drought may be the result of long term meteorological droughts that results in the drying up of reservoirs, lakes, streams, rivers and a decline in groundwater levels (Rathore 2004).
'
        }
      ]
    },

    // region pie-charts
    {
      type: 'Region',
      className: 'region-piesI row',
      children: [
        {
          type: 'Chart',
          cardStyle: 'chart',
          header: 'Standard Precipitation Index',
          dataHandlers: ['getBarChartData'],
          settings: {
            type: 'multiBarChart',
            x: 'x',
            y: 'y',
            height: 800
          }
        },
      ]
    },
    {
      type: 'Region',
      className: 'region region-footer row',
      children: [
        {
          type: 'Markup',
          content: '
The Standardized Precipitation Index (SPI) is a tool which was developed primarily for defining and monitoring drought. It allows an analyst to determine the rarity of a drought at a given time scale (temporal resolution) of interest for any rainfall station with historic data. It can also be used to determine periods of anomalously wet events. The SPI is not a drought prediction tool. (http://drought.unl.edu/portals/0/docs/spi-program-alternative-method.pdf)
'
        }
      ]
    },
    /*{
      type: 'Region',
      className: 'region-piesI row',
      children: [
        {
          type: 'Chart',
          cardStyle: 'chart',
          header: 'Chart 1',
          style: {borderRight: '1px dotted gray'},
          className: 'col-md-6',
          data: [[{x: 1, y: 40}, {x: 2, y: 40}, {x: 3, y: 20}]],
          dataHandlers: ['NVD3.toPieChartSeries'],
          settings: {
            type: 'pieChart',
            x: 'x',
            y: 'y',
            height: 400
          },
        },
        {
          type: 'Chart',
          cardStyle: 'chart',
          header: 'Chart 2',
          className: 'col-md-6',
          data: [[{x: 1, y: 40}, {x: 2, y: 40}, {x: 3, y: 20}]],
          dataHandlers: ['NVD3.toPieChartSeries'],
          settings: {
            type: 'pieChart',
            x: 'x',
            y: 'y',
            height: 400
          },
        },
      ]
    }, */

    {
      type: 'Region',
      className: 'region region-footer',
      children: [
        {
          type: 'Chart',
          cardStyle: 'chart',
          header: 'Average Temperature',
          settings: {
            type: 'lineChart',
            forceY: ['-20', '100'],
            x: 'time',
            y: 'TAVG',
            xAxis: {
              tickFormat: formatTime
            },
          },
          dataHandlers: [
            {
              name: 'common.filterDashboardDataByParamEquals',
              field: 'StateCode',
              value: 50
            },
            {
              name: 'NVD3.returnChartSeries',
              series:
                [
                  {
                    name: 'TAVG',
                    color: 'purple'
                  }
                ]
            }
          ]
        },
        {
          type: 'Markup',
          content: '
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum
'
        }
      ]
    }
  ]
}
<commit_msg>Use remote data for dash<commit_after>import {time} from 'd3';

let formatTime = function (d) {
 return time.format('%b, %Y')(new Date(d));
}

const stateIds =
						[
							{ value: 1, label: 'AK' },
							{ value: 30, label: 'NY' },
							{ value: 2, label: 'AZ' },
							{ value: 31, label: 'NC' },
							{ value: 3, label: 'AR' },
							{ value: 32, label: 'ND' },
							{ value: 4, label: 'CA' },
							{ value: 33, label: 'OH' },
							{ value: 5, label: 'CO' },
							{ value: 34, label: 'OK' },
							{ value: 6, label: 'CT' },
							{ value: 35, label: 'OR' },
							{ value: 7, label: 'DE' },
							{ value: 36, label: 'PA' },
							{ value: 8, label: 'FL' },
							{ value: 7, label: 'RI' },
							{ value: 9, label: 'GA' },
							{ value: 38, label: 'SC' },
							{ value: 10, label: 'ID' },
							{ value: 39, label: 'SD' },
							{ value: 11, label: 'IL' },
							{ value: 40, label: 'TN' },
							{ value: 12, label: 'IA' },
							{ value: 41, label: 'TX' },
							{ value: 13, label: 'IA' },
							{ value: 42, label: 'UT' },
							{ value: 14, label: 'KS' },
							{ value: 43, label: 'VT' },
							{ value: 15, label: 'KT' },
							{ value: 44, label: 'VA' },
							{ value: 16, label: 'LA' },
							{ value: 45, label: 'WA' },
							{ value: 17, label: 'ME' },
							{ value: 46, label: 'WV' },
							{ value: 18, label: 'MD' },
							{ value: 47, label: 'WI' },
							{ value: 19, label: 'MA' },
							{ value: 48, label: 'WY' },
							{ value: 20, label: 'MI' },
							{ value: 21, label: 'MN' },
							{ value: 22, label: 'MI' },
							{ value: 103, label: 'Central Region' },
							{ value: 23, label: 'MO' },
							{ value: 24, label: 'MT' },
							{ value: 25, label: 'NE' },
							{ value: 106, label: 'South Region' },
							{ value: 26, label: 'NV' },
							{ value: 27, label: 'NH' },
							{ value: 28, label: 'NJ' },
							{ value: 29, label: 'NM' },
              { value: 50, label: 'USA?'}
						]

export var settings = {
  title: 'React-Dash Demo -- Climate Indices by Year',
  queries: {},
	// we will add this to globalData in app.js
  // and use these labels in our customDatahandlers
  climate_vars: {
    PCP: 'Precipitation Index',
    TAVG: 'Temperature Index',
    TMIN: 'Minimum Temperature Index',
    TMAX: 'Maximum Temperature Index',
    PDSI: 'Palmer Drought Severity Index',
    PHDI: 'Palmer Hydrological Drought Index',
    ZNDX: 'Palmer Z-Index',
    PMDI: 'Modified Palmer Drought Severity Index',
    CDD: 'Cooling Degree Days',
    HDD: 'Heating Degree Days',
    SPnn: 'Standard Precipitation Index'
	},

  // if defined at the top level, fetchData will populate
  // the dashboard with globalData
  fetchData: {
    type: 'backend',
    backend: 'csv',
    url: 'https://dl.dropboxusercontent.com/u/73703010/react_dash_data_0.4/climate_indices.csv'
  },
  // if applied at the top level, datahandlers will filter global data
  dataHandlers: ['filterData'],

  components: [

    // region top
    {
      type: 'Region',
      cardStyle: 'content',
      header: "Climate Metrics",
      className: 'region region-top row',
      children: [
        {
          type: 'Autocomplete',
          cid: 'a1',
          field: 'year',
          placeholder: 'Select year...',
          dataHandlers: [{name: 'common.getEventReturn'}],
          initVal: '2015',
          fetch: true,
          options: [
            { label: '2010', value: '2010' },
            { label: '2011', value: '2011' },
            { label: '2012', value: '2012' },
            { label: '2013', value: '2013' },
            { label: '2014', value: '2014' },
            { label: '2015', value: '2015' },
          ]
        },
      ]
    },
    {
      type: 'Region',
      className: 'region-metrics region row',
      children: [
        {
          type: 'Metric',
          cardStyle: 'metric',
          iconClass: 'fa fa-level-up',
          className: 'col-md-4',
          caption: 'Maximum Temp.',
          dataHandlers: ['getMaxTemp']
        },
        {
          type: 'Metric',
          cardStyle: 'metric',
          iconClass: 'fa fa-level-down',
          className: 'col-md-4',
          background: '#53ACC9',
          caption: 'Minimum Temp.',
          dataHandlers: ['getMinTemp']
        },
        {
          type: 'Metric',
          cardStyle: 'metric',
          iconClass: 'fa fa-fire',
          className: 'col-md-4',
          caption: 'Average Temp/',
          background: '#C97053',
          dataHandlers: ['getAvgTemp']
        }
      ]
    },

    // region choropleth
    {
      type: 'Region',
      className: 'region-choropleth',
      children: [
        // choropleth
        {
          type: 'Choropleth',
          cid: 'choro1',
          cardStyle: 'map',
          header: 'Palmer Hydrological Drought Index',
          format: 'topojson',
          dataHandlers: [
            {
              name: 'getMapData',
              stateArray: stateIds
            }
          ],
          dataKeyField: 'name',
          dataValueField: 'PHDI',
          geometryKeyField: 'name',
          geometry: 'https://dl.dropboxusercontent.com/u/73703010/react_dash_data_0.4/map/usa.json', // topojson or geojson
          projection: 'albersUsa', // https://github.com/d3/d3/wiki/Geo-Projections
          scaleDenominator: .8,
          borderColor: 'white',
          noDataColor: 'red',
          topologyObject: 'usa',
          startColor: 'red',
          endColor: 'yellow',
          dataClassification: 'equidistant',
          legend: {
            classesCount: 5,
            palleteKey: 'GnBu',
            pallete: ['#ff3333','#ff4d4d','#ff6666','#ff8080','#ff9999','#ffb3b3','#ffcccc'],
          },
        },
      ]
    },
    {
      type: 'Region',
      className: 'region region-lower row',
      children: [
        {
          type: 'Markup',
          content: '
Hydrological drought is described as a sustained and regionally extensive occurrence of below average natural water availability (Tallaksen and van Lanen, 2004). Hydrological drought as period of time below the average water content in streams, reservoirs, groundwater aquifers, lakes and soils. The period is associated effects of precipitation (including snowfall) shortfall on surface and subsurface water supply, rather than with direct shortfall in precipitation (Yevjevich et al., 1977). Hydrological drought may be the result of long term meteorological droughts that results in the drying up of reservoirs, lakes, streams, rivers and a decline in groundwater levels (Rathore 2004).
'
        }
      ]
    },

    // region pie-charts
    {
      type: 'Region',
      className: 'region-piesI row',
      children: [
        {
          type: 'Chart',
          cardStyle: 'chart',
          header: 'Standard Precipitation Index',
          dataHandlers: ['getBarChartData'],
          settings: {
            type: 'multiBarChart',
            x: 'x',
            y: 'y',
            height: 800
          }
        },
      ]
    },
    {
      type: 'Region',
      className: 'region region-footer row',
      children: [
        {
          type: 'Markup',
          content: '
The Standardized Precipitation Index (SPI) is a tool which was developed primarily for defining and monitoring drought. It allows an analyst to determine the rarity of a drought at a given time scale (temporal resolution) of interest for any rainfall station with historic data. It can also be used to determine periods of anomalously wet events. The SPI is not a drought prediction tool. (http://drought.unl.edu/portals/0/docs/spi-program-alternative-method.pdf)
'
        }
      ]
    },
    /*{
      type: 'Region',
      className: 'region-piesI row',
      children: [
        {
          type: 'Chart',
          cardStyle: 'chart',
          header: 'Chart 1',
          style: {borderRight: '1px dotted gray'},
          className: 'col-md-6',
          data: [[{x: 1, y: 40}, {x: 2, y: 40}, {x: 3, y: 20}]],
          dataHandlers: ['NVD3.toPieChartSeries'],
          settings: {
            type: 'pieChart',
            x: 'x',
            y: 'y',
            height: 400
          },
        },
        {
          type: 'Chart',
          cardStyle: 'chart',
          header: 'Chart 2',
          className: 'col-md-6',
          data: [[{x: 1, y: 40}, {x: 2, y: 40}, {x: 3, y: 20}]],
          dataHandlers: ['NVD3.toPieChartSeries'],
          settings: {
            type: 'pieChart',
            x: 'x',
            y: 'y',
            height: 400
          },
        },
      ]
    }, */

    {
      type: 'Region',
      className: 'region region-footer',
      children: [
        {
          type: 'Chart',
          cardStyle: 'chart',
          header: 'Average Temperature',
          settings: {
            type: 'lineChart',
            forceY: ['-20', '100'],
            x: 'time',
            y: 'TAVG',
            xAxis: {
              tickFormat: formatTime
            },
          },
          dataHandlers: [
            {
              name: 'common.filterDashboardDataByParamEquals',
              field: 'StateCode',
              value: 50
            },
            {
              name: 'NVD3.returnChartSeries',
              series:
                [
                  {
                    name: 'TAVG',
                    color: 'purple'
                  }
                ]
            }
          ]
        },
        {
          type: 'Markup',
          content: '
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum
'
        }
      ]
    }
  ]
}
<|endoftext|>


<commit_before>var Settings = require("../Settings");

exports.prepare = function (Model, associations, association_properties) {
	Model.hasOne = function (name, OtherModel, opts) {
		if (typeof OtherModel == "object" && !OtherModel.table) {
			opts = OtherModel;
			OtherModel = null;
		}
		opts = opts || {};

		var assocName = opts.name || ucfirst(name);
		var association = {
			name           : name,
			model          : OtherModel || Model,
			reversed       : opts.reversed,
			autoFetch      : opts.autoFetch || false,
			autoFetchLimit : opts.autoFetchLimit || 2,
			field          : opts.field || Model.settings.get("properties.association_key").replace("{name}", name),
			required       : 'required' in opts ? opts.required : true,
			getAccessor    : opts.getAccessor || ("get" + assocName),
			setAccessor    : opts.setAccessor || ("set" + assocName),
			hasAccessor    : opts.hasAccessor || ("has" + assocName),
			delAccessor    : opts.delAccessor || ("remove" + assocName)
		};
		associations.push(association);
		association_properties.push(association.field);

		if (opts.reverse) {
			OtherModel.hasOne(opts.reverse, Model, {
				reversed       : true,
				field          : association.field,
				autoFetch      : association.autoFetch,
				autoFetchLimit : association.autoFetchLimit
			});
		}
		return this;
	};
};

exports.extend = function (Model, Instance, Driver, associations, opts, cb) {
	if (associations.length === 0) {
		return cb();
	}

	var pending = associations.length;
	var extendDone = function extendDone() {
		pending -= 1;

		if (pending === 0) {
			return cb();
		}
	};

	for (var i = 0; i < associations.length; i++) {
		extendInstance(Model, Instance, Driver, associations[i], opts, extendDone);
	}
};

function extendInstance(Model, Instance, Driver, association, opts, cb) {
	Object.defineProperty(Instance, association.hasAccessor, {
		value: function (opts, cb) {
			if (typeof opts == "function") {
				cb = opts;
				opts = {};
			}

			if (Instance[association.field]) {
				association.model.get(Instance[association.field], opts, function (err, instance) {
					return cb(err, instance ? true : false);
				});
			} else {
				cb(null, false);
			}

			return this;
		},
		enumerable: false
	});
	Object.defineProperty(Instance, association.getAccessor, {
		value: function (opts, cb) {
			if (typeof opts == "function") {
				cb = opts;
				opts = {};
			}

			if (association.reversed) {
				if (Instance[Model.id]) {
					var conditions = {};
					conditions[association.field] = Instance[Model.id];
					association.model.find(conditions, opts, cb);
				} else {
					cb(null);
				}
			} else {
				if (!Instance.hasOwnProperty(association.field)) {
					association.model.get(Instance[Model.id], function (err, instance) {
						if (err || !instance[association.field]) {
							return cb(null);
						}
						association.model.get(instance[association.field], opts, cb);
					});
				} else if (Instance[association.field]) {
					association.model.get(Instance[association.field], opts, cb);
				} else {
					cb(null);
				}
			}

			return this;
		},
		enumerable: false
	});
	Object.defineProperty(Instance, association.setAccessor, {
		value: function (OtherInstance, cb) {
			if (association.reversed) {
				Instance.save(function (err) {
					if (err) {
						return cb(err);
					}

					OtherInstance[association.field] = Instance[Model.id];

					return OtherInstance.save(cb);
				});
			} else {
				OtherInstance.save(function (err) {
					if (err) {
						return cb(err);
					}

					Instance[association.field] = OtherInstance[association.model.id];

					return Instance.save(cb);
				});
			}

			return this;
		},
		enumerable: false
	});
	if (!association.reversed) {
		Object.defineProperty(Instance, association.delAccessor, {
			value: function (cb) {
				Instance[association.field] = 0;
				Instance.save(cb);

				return this;
			},
			enumerable: false
		});
	}

	if (!opts.hasOwnProperty("autoFetchLimit") || typeof opts.autoFetchLimit == "undefined") {
		opts.autoFetchLimit = association.autoFetchLimit;
	}

	if (opts.autoFetchLimit === 0 || (!opts.autoFetch && !association.autoFetch)) {
		return cb();
	}

	Instance[association.getAccessor]({ autoFetchLimit: opts.autoFetchLimit - 1 }, function (err, Assoc) {
		if (!err) {
			Instance[association.name] = Assoc;
		}

		return cb();
	});
}

function ucfirst(text) {
	return text[0].toUpperCase() + text.substr(1);
}
<commit_msg>Make required false by default<commit_after>var Settings = require("../Settings");

exports.prepare = function (Model, associations, association_properties) {
	Model.hasOne = function (name, OtherModel, opts) {
		if (typeof OtherModel == "object" && !OtherModel.table) {
			opts = OtherModel;
			OtherModel = null;
		}
		opts = opts || {};

		var assocName = opts.name || ucfirst(name);
		var association = {
			name           : name,
			model          : OtherModel || Model,
			reversed       : opts.reversed,
			autoFetch      : opts.autoFetch || false,
			autoFetchLimit : opts.autoFetchLimit || 2,
			field          : opts.field || Model.settings.get("properties.association_key").replace("{name}", name),
			required       : opts.required || false,
			getAccessor    : opts.getAccessor || ("get" + assocName),
			setAccessor    : opts.setAccessor || ("set" + assocName),
			hasAccessor    : opts.hasAccessor || ("has" + assocName),
			delAccessor    : opts.delAccessor || ("remove" + assocName)
		};
		associations.push(association);
		association_properties.push(association.field);

		if (opts.reverse) {
			OtherModel.hasOne(opts.reverse, Model, {
				reversed       : true,
				field          : association.field,
				autoFetch      : association.autoFetch,
				autoFetchLimit : association.autoFetchLimit
			});
		}
		return this;
	};
};

exports.extend = function (Model, Instance, Driver, associations, opts, cb) {
	if (associations.length === 0) {
		return cb();
	}

	var pending = associations.length;
	var extendDone = function extendDone() {
		pending -= 1;

		if (pending === 0) {
			return cb();
		}
	};

	for (var i = 0; i < associations.length; i++) {
		extendInstance(Model, Instance, Driver, associations[i], opts, extendDone);
	}
};

function extendInstance(Model, Instance, Driver, association, opts, cb) {
	Object.defineProperty(Instance, association.hasAccessor, {
		value: function (opts, cb) {
			if (typeof opts == "function") {
				cb = opts;
				opts = {};
			}

			if (Instance[association.field]) {
				association.model.get(Instance[association.field], opts, function (err, instance) {
					return cb(err, instance ? true : false);
				});
			} else {
				cb(null, false);
			}

			return this;
		},
		enumerable: false
	});
	Object.defineProperty(Instance, association.getAccessor, {
		value: function (opts, cb) {
			if (typeof opts == "function") {
				cb = opts;
				opts = {};
			}

			if (association.reversed) {
				if (Instance[Model.id]) {
					var conditions = {};
					conditions[association.field] = Instance[Model.id];
					association.model.find(conditions, opts, cb);
				} else {
					cb(null);
				}
			} else {
				if (!Instance.hasOwnProperty(association.field)) {
					association.model.get(Instance[Model.id], function (err, instance) {
						if (err || !instance[association.field]) {
							return cb(null);
						}
						association.model.get(instance[association.field], opts, cb);
					});
				} else if (Instance[association.field]) {
					association.model.get(Instance[association.field], opts, cb);
				} else {
					cb(null);
				}
			}

			return this;
		},
		enumerable: false
	});
	Object.defineProperty(Instance, association.setAccessor, {
		value: function (OtherInstance, cb) {
			if (association.reversed) {
				Instance.save(function (err) {
					if (err) {
						return cb(err);
					}

					OtherInstance[association.field] = Instance[Model.id];

					return OtherInstance.save(cb);
				});
			} else {
				OtherInstance.save(function (err) {
					if (err) {
						return cb(err);
					}

					Instance[association.field] = OtherInstance[association.model.id];

					return Instance.save(cb);
				});
			}

			return this;
		},
		enumerable: false
	});
	if (!association.reversed) {
		Object.defineProperty(Instance, association.delAccessor, {
			value: function (cb) {
				Instance[association.field] = 0;
				Instance.save(cb);

				return this;
			},
			enumerable: false
		});
	}

	if (!opts.hasOwnProperty("autoFetchLimit") || typeof opts.autoFetchLimit == "undefined") {
		opts.autoFetchLimit = association.autoFetchLimit;
	}

	if (opts.autoFetchLimit === 0 || (!opts.autoFetch && !association.autoFetch)) {
		return cb();
	}

	Instance[association.getAccessor]({ autoFetchLimit: opts.autoFetchLimit - 1 }, function (err, Assoc) {
		if (!err) {
			Instance[association.name] = Assoc;
		}

		return cb();
	});
}

function ucfirst(text) {
	return text[0].toUpperCase() + text.substr(1);
}
<|endoftext|>


<commit_before>"use strict";

var http = require('http')
   ,Promise = require('bluebird')
   ,co = require('../lib/safeco')
   ,wait = require('co-wait')
   ,read = require('co-read')
   ,FlakeId = require('flake-idgen')
   ,restify = require('restify')
   ,levelup = require('levelup')
   ,format  = require('biguint-format')
   ,Container = require('./Container')
   ,logger = require('./logger')
;

Promise.longStackTraces();

/**
 * The container manager object that contains all routes.
 */
var Server = function() {
  this.configure = this.configure.bind(this);
  this.run = this.run.bind(this);
  this.flakeIdGen = new FlakeId();
  for (let verb in this.routes) {
    for (let route in this.routes[verb]) {
      this.routes[verb][route] = this.routes[verb][route].bind(this);
    }
  }
}

/**
 * Starts the server.
 */
Server.prototype.run = function(probo, done) {
  var self = this;
  this.server.listen(probo.config.port, function(error) {
    self.log.info('server started and listening on ' + self.server.url);
    done(error);
  });
}

/**
 * Configures the server.
 */
Server.prototype.configure = function(config, done) {
  var self = this;
  this.config = config
  this.log = logger.getLogger('container-manager');

  var API = require('./api');

  this.api = API.getAPI({
    url: this.config.api.url,
    token: this.config.api.token,
    log: this.log,
    handler: {},  // TODO: URL for the provider handler
  })

  var server = restify.createServer({
    name: config.name,
    version: require('../package').version,
    log: this.log.child({component: 'http'}),
  });
  server.use(restify.acceptParser(server.acceptable));
  server.use(restify.queryParser());
  server.use(restify.bodyParser());

  // Extend logger using the plugin.
  server.use(restify.requestLogger({
    serializers: restify.bunyan.serializers
  }));
  server.use(function (req, res, next) {
    req.log.info({req: req}, 'REQUEST');
    next();
  });
  server.on('after', restify.auditLogger({
    log: server.log
  }));
  server.on('uncaughtException', function (req, res, route, err) {
    console.log("uncaughtException", err.stack)
    req.log.error({err: err}, "uncaughtException");
    //err._customContent = 'something is wrong!';
  });

  for (var verb in this.routes) {
    for (var route in this.routes[verb]) {
      server[verb](config.prefix + '/' + route, this.routes[verb][route]);
    }
  }

  this.server = server;
  // Allow the db to be handed in with the config.
  this.db = config.levelupDB || levelup(config.dataDir, { valueEncoding: 'json' });

  // update safeco's error handler with bunyan logging one
  co = co(function(err, opts){
    (opts.req || self).log.error({err: err}, "Uncaught co Exception")

    if(opts.res){
      try {
        opts.res.send(err)
      } catch(e){
        // headers already sent, reponse started
        // write error to the response and end it
        var req_id_str = opts.req ? `(req id ${opts.req.id()})` : ''
        opts.res.end(`\n\nERROR: ${err.message}, terminating response ${req_id_str}`)
      }
    }

    // let middleware continue
    if(opts.next){
      opts.next()
    }
  })

  done();
}

/**
 * Define empty route structures so that it is easy to layer them on below.
 */
Server.prototype.routes = {
  get: {},
  post: {},
  put: {},
  del: {},
};

/**
 * Create a new build in a new envrionment.
 */
Server.prototype.routes.post['startbuild'] = function(req, res, next) {
  var body = req.body, // {build, project}
      build = body.build,
      project = body.project;

  build.project = project;
  var self = this;
  var steps

  co(function* (){
    try {
      yield self.storeBuildData(build)
    } catch(error){
      req.log.error({err: error}, "Failed to write build to the BD")
      res.json(400, {error: error.message});
      return;
    }

    var image = build.config.image || self.config.defaultImage
    var containerName = `probo-${project.slug.replace('/', '.')}-${project.id}-${build.id}`

    var container_config = {
      // config from the build
      docker: self.config.docker,
      jobConfig: build.config,
      containerName: containerName,
      containerId: containerName,
      image: image,
      imageConfig: self.config.images[image],

      // global config
      binds: self.config.binds,
      // attachLogs: true, // set to true to attach 'docker -f logs' to stdout of this process
    };

    req.log.info(`Starting container build: ${containerName}`)
    // req.log.debug({build, container_config}, "Starting container build")

    // attach logger
    container_config.log = req.log;

    var container = new Container(container_config);

    var statusUpdater = function(build, context){
      var postStatus = Promise.promisify(self.api.setBuildStatus.bind(self.api))

      return function* (status){
        try {
          yield postStatus(build, `probo/${context}`, status)

          req.log.info({status}, "status updated")
        } catch (e){
          req.log.error({err: e}, "Failed to update build status")
        }
      }
    }

    var updateEnvStatus = statusUpdater(build, "env")

    // gather all the tasks
    var tasks = yield container.buildTasks()

    yield updateEnvStatus({state: "pending", description: "[running] The hamsters are working hard on your setup"})
    for(let task of tasks){
      // first, set all tasks to pending state
      let task_slug = `${task.options.plugin}/${task.name}`
      task.updateStatus = statusUpdater(build, task_slug)
      yield task.updateStatus({state: "pending", description: `[pending] ${task.description()}`})
    }

    // we're in no hurry!
    yield wait(5000)

    try {
      yield container.create() // returns output of container.inspect
      yield updateEnvStatus({state: "success", description: "[finished] Environment built"})
    } catch(e){
      switch(e.statusCode){
      case 409: // container conflict, reuse existing container
        req.log.info(`Container ${containerName} is already running, reusing it`)
        break
      default:
        req.log.error({err: e}, "Unknown container error")
        res.json(500, {error: e.message});

        yield updateEnvStatus({state: "error", description: e.message})
        return next()
      }
    }

    // we're in no hurry!
    yield wait(2000)

    // return a response to the requestor

    res.json({
      status: "build started",
      container: {
        id: container.container.id || null,
        name: containerName
      },
      build: {
        id: build.id
      },
      steps: tasks.length
    })
    next()

    // continue processing the build and firing off events
    var log = req.log
    try {
      // steps is an array of Plugins with promisified run methods
      for(let task of tasks){
        // first, yield step to kick off the run
        log.debug("Kicking off step")

        log.debug("Step exec started:", task.name)

        // let updateTaskStatus = statusUpdater(build, task_slug)
        yield task.updateStatus({state: "pending", description: `[running] ${task.description()}`})

        let result = yield task.run
        //let task = result.provisioner

        try {
          // stream output back to the log (for now)
          log.debug(`${task.name} OUTPUT`)
          var chunk
          while((chunk = yield read(result.stream))){
            log.debug(chunk.toString().trim())
          }

          // check exit code
          var data = yield result.exec // returns result of container.inspect call
          var exit_code = data.ExitCode
          log.debug(`EXIT_CODE: ${exit_code}`)

          yield task.updateStatus({
            state: exit_code === 0 ? "success" : "error",
            description: `[finished] ${task.description()}`
          })
        } catch(e){
          log.error({err: e}, "Task execution failed")

          yield task.updateStatus({state: "error", description: e.message})
        }
      }

      log.debug('ALL STEPS COMPLETED')
    } catch(error){
      log.error({err: error}, "Error processing steps")
    }
  }, {req, res, next})
};

/**
 * List builds on this server.
 */
Server.prototype.routes.get['builds'] = function(req, res) {
  res.writeHead(200);
  res.write("[\n");
  var readStream = this.db.createValueStream({
    gte: 'builds!!',
    lte: 'builds!~',
    valueEncoding: 'json',
  });
  var item = 0;
  readStream.on('data', function(data) {
    if (item > 0) res.write(",\n");
    res.write(JSON.stringify(data) + "\n");
    item++;
  });
  readStream.on('end', function() {
    res.end(']');
  });
};

/**
 * List builds on this server.
 */
Server.prototype.routes.get['builds/:id'] = function(req, res) {
  this.db.get('builds!' + req.params.id, { valueEncoding: 'json' }, function(error, data) {
    if (error) return res.send(404, error);
    res.send(data);
  });
};

/**
 * Delete an individual build.
 */
Server.prototype.routes.del['builds/:id'] = function(req, res) {
  this.log.info('deleting build ' + req.params.id);
  this.db.del('builds!' + req.params.id, function(error) {
    if (error) return res.send(500, error);
    res.send('success');
  });
};

/**
 * Store build data.
 */
Server.prototype.storeBuildData = Promise.promisify(function(data, done) {
  if (!data.id) {
    data.id = format(this.flakeIdGen.next(), 'hex');
  }
  this.db.put('builds!' + data.id, data, function(error) {
    done(error, data);
  });
});

module.exports = Server;
<commit_msg>pretty icons!<commit_after>"use strict";

var http = require('http')
   ,Promise = require('bluebird')
   ,co = require('../lib/safeco')
   ,wait = require('co-wait')
   ,read = require('co-read')
   ,FlakeId = require('flake-idgen')
   ,restify = require('restify')
   ,levelup = require('levelup')
   ,format  = require('biguint-format')
   ,Container = require('./Container')
   ,logger = require('./logger')
;

Promise.longStackTraces();

/**
 * The container manager object that contains all routes.
 */
var Server = function() {
  this.configure = this.configure.bind(this);
  this.run = this.run.bind(this);
  this.flakeIdGen = new FlakeId();
  for (let verb in this.routes) {
    for (let route in this.routes[verb]) {
      this.routes[verb][route] = this.routes[verb][route].bind(this);
    }
  }
}

/**
 * Starts the server.
 */
Server.prototype.run = function(probo, done) {
  var self = this;
  this.server.listen(probo.config.port, function(error) {
    self.log.info('server started and listening on ' + self.server.url);
    done(error);
  });
}

/**
 * Configures the server.
 */
Server.prototype.configure = function(config, done) {
  var self = this;
  this.config = config
  this.log = logger.getLogger('container-manager');

  var API = require('./api');

  this.api = API.getAPI({
    url: this.config.api.url,
    token: this.config.api.token,
    log: this.log,
    handler: {},  // TODO: URL for the provider handler
  })

  var server = restify.createServer({
    name: config.name,
    version: require('../package').version,
    log: this.log.child({component: 'http'}),
  });
  server.use(restify.acceptParser(server.acceptable));
  server.use(restify.queryParser());
  server.use(restify.bodyParser());

  // Extend logger using the plugin.
  server.use(restify.requestLogger({
    serializers: restify.bunyan.serializers
  }));
  server.use(function (req, res, next) {
    req.log.info({req: req}, 'REQUEST');
    next();
  });
  server.on('after', restify.auditLogger({
    log: server.log
  }));
  server.on('uncaughtException', function (req, res, route, err) {
    console.log("uncaughtException", err.stack)
    req.log.error({err: err}, "uncaughtException");
    //err._customContent = 'something is wrong!';
  });

  for (var verb in this.routes) {
    for (var route in this.routes[verb]) {
      server[verb](config.prefix + '/' + route, this.routes[verb][route]);
    }
  }

  this.server = server;
  // Allow the db to be handed in with the config.
  this.db = config.levelupDB || levelup(config.dataDir, { valueEncoding: 'json' });

  // update safeco's error handler with bunyan logging one
  co = co(function(err, opts){
    (opts.req || self).log.error({err: err}, "Uncaught co Exception")

    if(opts.res){
      try {
        opts.res.send(err)
      } catch(e){
        // headers already sent, reponse started
        // write error to the response and end it
        var req_id_str = opts.req ? `(req id ${opts.req.id()})` : ''
        opts.res.end(`\n\nERROR: ${err.message}, terminating response ${req_id_str}`)
      }
    }

    // let middleware continue
    if(opts.next){
      opts.next()
    }
  })

  done();
}

/**
 * Define empty route structures so that it is easy to layer them on below.
 */
Server.prototype.routes = {
  get: {},
  post: {},
  put: {},
  del: {},
};

/**
 * Create a new build in a new envrionment.
 */
Server.prototype.routes.post['startbuild'] = function(req, res, next) {
  var body = req.body, // {build, project}
      build = body.build,
      project = body.project;

  build.project = project;
  var self = this;
  var steps

  co(function* (){
    try {
      yield self.storeBuildData(build)
    } catch(error){
      req.log.error({err: error}, "Failed to write build to the BD")
      res.json(400, {error: error.message});
      return;
    }

    var image = build.config.image || self.config.defaultImage
    var containerName = `probo-${project.slug.replace('/', '.')}-${project.id}-${build.id}`

    var container_config = {
      // config from the build
      docker: self.config.docker,
      jobConfig: build.config,
      containerName: containerName,
      containerId: containerName,
      image: image,
      imageConfig: self.config.images[image],

      // global config
      binds: self.config.binds,
      // attachLogs: true, // set to true to attach 'docker -f logs' to stdout of this process
    };

    req.log.info(`Starting container build: ${containerName}`)
    // req.log.debug({build, container_config}, "Starting container build")

    // attach logger
    container_config.log = req.log;

    var container = new Container(container_config);

    var icons = {
      running: "➠",
      pending: "⌛",
      finished: "✔"
    }

    var statusUpdater = function(build, context){
      var postStatus = Promise.promisify(self.api.setBuildStatus.bind(self.api))

      return function* (status){
        try {
          yield postStatus(build, `probo/${context}`, status)

          req.log.info({status}, "status updated")
        } catch (e){
          req.log.error({err: e}, "Failed to update build status")
        }
      }
    }

    var updateEnvStatus = statusUpdater(build, "env")

    // gather all the tasks
    var tasks = yield container.buildTasks()

    yield updateEnvStatus({state: "pending", description: `[${icons.running}] The hamsters are working hard on your setup`})
    for(let task of tasks){
      // first, set all tasks to pending state
      let task_slug = `${task.options.plugin}/${task.name}`
      task.updateStatus = statusUpdater(build, task_slug)
      yield task.updateStatus({state: "pending", description: `[${icons.pending}] ${task.description()}`})
    }

    // we're in no hurry!
    yield wait(5000)

    try {
      yield container.create() // returns output of container.inspect
      yield updateEnvStatus({state: "success", description: `[${icons.finished}] Environment built`})
    } catch(e){
      switch(e.statusCode){
      case 409: // container conflict, reuse existing container
        req.log.info(`Container ${containerName} is already running, reusing it`)
        break
      default:
        req.log.error({err: e}, "Unknown container error")
        res.json(500, {error: e.message});

        yield updateEnvStatus({state: "error", description: e.message})
        return next()
      }
    }

    // we're in no hurry!
    yield wait(2000)

    // return a response to the requestor

    res.json({
      status: "build started",
      container: {
        id: container.container.id || null,
        name: containerName
      },
      build: {
        id: build.id
      },
      steps: tasks.length
    })
    next()

    // continue processing the build and firing off events
    var log = req.log
    try {
      // steps is an array of Plugins with promisified run methods
      for(let task of tasks){
        // first, yield step to kick off the run
        log.debug("Kicking off step")

        log.debug("Step exec started:", task.name)

        // let updateTaskStatus = statusUpdater(build, task_slug)
        yield task.updateStatus({state: "pending", description: `[${icons.running}] ${task.description()}`})

        let result = yield task.run

        try {
          // stream output back to the log (for now)
          log.debug(`${task.name} OUTPUT`)
          var chunk
          while((chunk = yield read(result.stream))){
            log.debug(chunk.toString().trim())
          }

          // check exit code
          var data = yield result.exec // returns result of container.inspect call
          var exit_code = data.ExitCode
          log.debug(`EXIT_CODE: ${exit_code}`)

          yield task.updateStatus({
            state: exit_code === 0 ? "success" : "error",
            description: `[${icons.finished}] ${task.description()}`
          })
        } catch(e){
          log.error({err: e}, "Task execution failed")

          yield task.updateStatus({state: "error", description: e.message})
        }
      }

      log.debug('ALL STEPS COMPLETED')
    } catch(error){
      log.error({err: error}, "Error processing steps")
    }
  }, {req, res, next})
};

/**
 * List builds on this server.
 */
Server.prototype.routes.get['builds'] = function(req, res) {
  res.writeHead(200);
  res.write("[\n");
  var readStream = this.db.createValueStream({
    gte: 'builds!!',
    lte: 'builds!~',
    valueEncoding: 'json',
  });
  var item = 0;
  readStream.on('data', function(data) {
    if (item > 0) res.write(",\n");
    res.write(JSON.stringify(data) + "\n");
    item++;
  });
  readStream.on('end', function() {
    res.end(']');
  });
};

/**
 * List builds on this server.
 */
Server.prototype.routes.get['builds/:id'] = function(req, res) {
  this.db.get('builds!' + req.params.id, { valueEncoding: 'json' }, function(error, data) {
    if (error) return res.send(404, error);
    res.send(data);
  });
};

/**
 * Delete an individual build.
 */
Server.prototype.routes.del['builds/:id'] = function(req, res) {
  this.log.info('deleting build ' + req.params.id);
  this.db.del('builds!' + req.params.id, function(error) {
    if (error) return res.send(500, error);
    res.send('success');
  });
};

/**
 * Store build data.
 */
Server.prototype.storeBuildData = Promise.promisify(function(data, done) {
  if (!data.id) {
    data.id = format(this.flakeIdGen.next(), 'hex');
  }
  this.db.put('builds!' + data.id, data, function(error) {
    done(error, data);
  });
});

module.exports = Server;
<|endoftext|>


<commit_before>#!/usr/bin/env node
'use strict';
var program = require('commander');
//var util = require('util');

var BBPromise = require('bluebird');
var json = require('../package.json');
var preq = require('preq');
var xmldom = require("xmldom");
var parser = new xmldom.DOMParser();
var compare = require('dom-compare').compare;
var reporter = require('dom-compare').GroupingReporter;
var assert = require('../test/utils/assert.js');
var server = require('../test/utils/server.js');
var fs = BBPromise.promisifyAll(require('fs'));
var path = require('path');

var baseURL = server.config.uri;

program
    .version(json.version)
    .usage('[options]')
    .option('-v, --verbose', 'Show verbose error information')
    .option('-f, --force', 'Replace images in the data folders.');

program.parse(process.argv);

var formulae = require('../test/files/mathjax-texvc/mathjax-texvc.json');
var mdPath = path.resolve(__dirname, "../test/files/mathjax-texvc/test.md");

return server.start().delay(1000).then(function () {
    console.log("Server started");
    if (program.force) {
        fs.writeFileSync(mdPath, "## Test overview");
    }
    return BBPromise.resolve(formulae);
}).each(function (testcase) {
    console.log("\n~~ " + testcase.id + " $" + testcase.input + "$ ~~");
    if (testcase.ignore) {
        console.log(testcase.id + " $" + testcase.input + "$ is ignored");
        return;
    }
    return preq.post({
        uri: baseURL + "svg/",
        body: {q: testcase.input, noSpeak: true}
    }).then(function (res) {
        var nextProm = BBPromise.resolve();
        var svgPath = path.resolve(__dirname, "../test/files/mathjax-texvc/svg", testcase.id + ".svg");
        var referenceSvg = fs.readFileSync(svgPath).toString();
        assert.status(res, 200);
        var actualSvg = res.body.toString();
        if (referenceSvg === actualSvg) {
            if (program.verbose) {
                console.log("SVG for testcase " + testcase.id + " has not changed.");
            }
        } else {
            console.log("SVG for testcase " + testcase.id + " has changed.");
            var domRef = parser.parseFromString(referenceSvg);
            var domReal = parser.parseFromString(actualSvg);
            var result = compare(domRef, domReal);
            console.log(reporter.report(result));
            if (program.force) {
                nextProm = fs.writeFileAsync(svgPath, actualSvg).catch(function(err) {
                    console.log(err);
                }).then(function() {
                    console.log("SVG File for " + testcase.id + " has been saved!");
                });
            }
        }
        return nextProm.then(function() {
            return preq.post({
                uri: baseURL + "png/",
                body: {q: testcase.input, noSpeak: true}
            });
        });
    }).then(function (res) {
        var nextProm = BBPromise.resolve();
        var pngPath = path.resolve(__dirname, "../test/files/mathjax-texvc/png", testcase.id + ".png");
        var referencePng = fs.readFileSync(pngPath, 'base64');
        assert.status(res, 200);
        var actualPng = res.body.toString().replace(/^data:image\/png;base64,/, "");
        if (referencePng === actualPng) {
            if (program.verbose) {
                console.log("PNG for testcase " + testcase.id + " has not changed.");
            }
        } else {
            console.log("PNG for testcase " + testcase.id + " has changed.");
            if (program.force) {
                nextProm = fs.writeFileAsync(pngPath, actualPng).catch(function(err) {
                    console.log(err);
                    return BBPromise.reject(err);
                }).then(function() {
                    console.log("PNG File for " + testcase.id + " has been saved!");
                });
            }
        }
        if (program.force) {
            nextProm = nextProm.then(function() {
                return fs.appendFileAsync(mdPath, "\n * Test " + testcase.id + " $" + testcase.input
                    + "$ ($"+ testcase.texvcjs + "$)![Test-image](png/" + testcase.id +
                    ".png) [Test-image-svg](svg/" + testcase.id + ".svg)")
                .catch(function(err) {
                    console.log(err);
                    return BBPromise.reject(err);
                }).then(function() {
                    console.log("MD File for " + testcase.id + " has been updated!");
                });
            });
        }
        return nextProm;
    });
}).then(server.stop).then(function () {
    process.exit(0);
});

<commit_msg>Fix typon in imgUpdate script<commit_after>#!/usr/bin/env node
'use strict';
var program = require('commander');
//var util = require('util');

var BBPromise = require('bluebird');
var json = require('../package.json');
var preq = require('preq');
var xmldom = require("xmldom");
var parser = new xmldom.DOMParser();
var compare = require('dom-compare').compare;
var reporter = require('dom-compare').GroupingReporter;
var assert = require('../test/utils/assert.js');
var server = require('../test/utils/server.js');
var fs = BBPromise.promisifyAll(require('fs'));
var path = require('path');

var baseURL = server.config.uri;

program
    .version(json.version)
    .usage('[options]')
    .option('-v, --verbose', 'Show verbose error information')
    .option('-f, --force', 'Replace images in the data folders.');

program.parse(process.argv);

var formulae = require('../test/files/mathjax-texvc/mathjax-texvc.json');
var mdPath = path.resolve(__dirname, "../test/files/mathjax-texvc/test.md");

return server.start().delay(1000).then(function () {
    console.log("Server started");
    if (program.force) {
        fs.writeFileSync(mdPath, "## Test overview");
    }
    return BBPromise.resolve(formulae);
}).each(function (testcase) {
    console.log("\n~~ " + testcase.id + " $" + testcase.input + "$ ~~");
    if (testcase.ignore) {
        console.log(testcase.id + " $" + testcase.input + "$ is ignored");
        return;
    }
    return preq.post({
        uri: baseURL + "svg/",
        body: {q: testcase.input, nospeech: true}
    }).then(function (res) {
        var nextProm = BBPromise.resolve();
        var svgPath = path.resolve(__dirname, "../test/files/mathjax-texvc/svg", testcase.id + ".svg");
        var referenceSvg = fs.readFileSync(svgPath).toString();
        assert.status(res, 200);
        var actualSvg = res.body.toString();
        if (referenceSvg === actualSvg) {
            if (program.verbose) {
                console.log("SVG for testcase " + testcase.id + " has not changed.");
            }
        } else {
            console.log("SVG for testcase " + testcase.id + " has changed.");
            var domRef = parser.parseFromString(referenceSvg);
            var domReal = parser.parseFromString(actualSvg);
            var result = compare(domRef, domReal);
            console.log(reporter.report(result));
            if (program.force) {
                nextProm = fs.writeFileAsync(svgPath, actualSvg).catch(function(err) {
                    console.log(err);
                }).then(function() {
                    console.log("SVG File for " + testcase.id + " has been saved!");
                });
            }
        }
        return nextProm.then(function() {
            return preq.post({
                uri: baseURL + "png/",
                body: {q: testcase.input, noSpeak: true}
            });
        });
    }).then(function (res) {
        var nextProm = BBPromise.resolve();
        var pngPath = path.resolve(__dirname, "../test/files/mathjax-texvc/png", testcase.id + ".png");
        var referencePng = fs.readFileSync(pngPath, 'base64');
        assert.status(res, 200);
        var actualPng = res.body.toString().replace(/^data:image\/png;base64,/, "");
        if (referencePng === actualPng) {
            if (program.verbose) {
                console.log("PNG for testcase " + testcase.id + " has not changed.");
            }
        } else {
            console.log("PNG for testcase " + testcase.id + " has changed.");
            if (program.force) {
                nextProm = fs.writeFileAsync(pngPath, actualPng).catch(function(err) {
                    console.log(err);
                    return BBPromise.reject(err);
                }).then(function() {
                    console.log("PNG File for " + testcase.id + " has been saved!");
                });
            }
        }
        if (program.force) {
            nextProm = nextProm.then(function() {
                return fs.appendFileAsync(mdPath, "\n * Test " + testcase.id + " $" + testcase.input
                    + "$ ($"+ testcase.texvcjs + "$)![Test-image](png/" + testcase.id +
                    ".png) [Test-image-svg](svg/" + testcase.id + ".svg)")
                .catch(function(err) {
                    console.log(err);
                    return BBPromise.reject(err);
                }).then(function() {
                    console.log("MD File for " + testcase.id + " has been updated!");
                });
            });
        }
        return nextProm;
    });
}).then(server.stop).then(function () {
    process.exit(0);
});

<|endoftext|>