<commit_before>/*
    Rob Holme

    Functions to highlight user specified fields on a HL7 message.
*/

// load modules
const vscode = require('vscode');
const common = require('./common.js');

// the list of fields to highlight
var fieldSelectionList = [];

// return codes for ExtractAllFields()
const HighlightFieldReturnCode = {
	ERROR_NO_LOCATION_PROVIDED: 0,
	SUCCESS_NO_FIELD_FOUND: 1,
	SUCCESS_FIELD_FOUND: 2
};

//----------------------------------------------------
// Highlight the HL7 field(s) identified by the user
// @param {string} itemLocation - a string identifying the field location (e.g. PID-3)
// @param {object} hl7Schema - An object containing the HL7 schema
// @param {string} backgroundColor - The RGBA colour value identifying the background colour e.g. "rgba(0,255,0,0.3)". If this is not supplied, a default colour will be used.
//
// @returns {int} - returns the number of decorations applied
function ShowHighlights(itemLocation, hl7Schema, backgroundColor) {
	// the default background colour for highlighted items (if not otherwise specified)
	const defaultBackgroundColour = 'rgba(0,255,0,0.3)';

	// load the message delimiters from the current file
	var delimiters = common.ParseDelimiters();
	// regex to match a segment name and field index
	var itemLocationRegex = new RegExp("^[A-Z]{2}([A-Z]|[0-9])[-]([0-9]{1,3})$", 'i');
	// associative array indexed by segment name, with the field index as a value.
	var locationHashtable = {};
	var highlightBackgroundColour = defaultBackgroundColour;
	var activeEditor = vscode.window.activeTextEditor;

	// dispose of decorations for previously highlighted fields.
	if (fieldSelectionList.length > 0) {
		currentDecoration.dispose();
		fieldSelectionList = [];
	}

	// return if no location provided by the user, or no active editor
	if ((!itemLocation) || (!activeEditor)) {
		return HighlightFieldReturnCode.ERROR_NO_LOCATION_PROVIDED;
	}

	// if a background colour was provided use it, otherwise the default background setting defined in defaultBackgroundColour will apply
	if (backgroundColor) {
		highlightBackgroundColour = backgroundColor
	}

	// create a decorator type that is used to decorate selected fields
	var highlightDecorationType = vscode.window.createTextEditorDecorationType({
		backgroundColor: highlightBackgroundColour
	});

	// Test to see if the user has provided a valid location string, otherwise assume a description has been provided instead
	if (itemLocationRegex.test(itemLocation)) {
		// identify the segment name and field index from the location string
		var segmentName = common.GetSegmentNameFromLocationString(itemLocation).toUpperCase();
		var fieldIndex = parseInt(common.GetFieldIndex(itemLocation), 10);
		// the first field of MSH segments is the field delimiter, adjust index accordingly for MSH fields
		if (segmentName == "MSH") {
			fieldIndex--;
		}
		locationHashtable[segmentName] = [fieldIndex];
	}
	// else assume the user has provided a field description to search for instead of a location.
	else {
		locationHashtable = common.FindLocationFromDescription(itemLocation, hl7Schema);
	}

	fieldSelectionList = AddDecorations(activeEditor, locationHashtable, delimiters.FIELD)
	// warn the user if the field selected does not exist in the message
	if (fieldSelectionList.length < 1) {
//		vscode.window.showWarningMessage("A field matching " + itemLocation + " could not be located in the message");
		return HighlightFieldReturnCode.SUCCESS_NO_FIELD_FOUND;
	}

	// apply the decoration to highlight the field.
	activeEditor.setDecorations(highlightDecorationType, fieldSelectionList);
	currentDecoration = highlightDecorationType;
	return HighlightFieldReturnCode.SUCCESS_FIELD_FOUND;
}

//----------------------------------------------------
//  Locate the position of the fields to highlight, add the highlight decoration to a list
// @param {object} activeEditor - the current vscode editor
// @param {object} locationHashtable - An hashtable containing field locations to highlight. e.g. PID-3
// @param {char} fieldDelimiter - the field delimiter used by the HL7 message. e.g. '|'
//
// @return {object} - returns an array of the decoration objects (background highlighting) for each of the locations identified by the locationHashtable parameter.
function AddDecorations(activeEditor, locationHashtable, fieldDelimiter) {
	var positionOffset = 0;
	var fieldSelectionListToReturn = [];
	var fieldRegEx = new RegExp("\\" + fieldDelimiter, "g");
	// calculate the number of characters at the end of line (<CR>, or <CR><LF>)
	var config = vscode.workspace.getConfiguration();
	var endOfLineLength = config.files.eol.length;

	for (lineIndex = 0; lineIndex < activeEditor.document.lineCount; lineIndex++) {
		var startPos = null;
		var endPos = null;
		var currentLine = activeEditor.document.lineAt(lineIndex).text;
		var fields = currentLine.split(fieldDelimiter);
		if (!(locationHashtable[fields[0].toUpperCase()] === undefined)) {
			var fieldCount = 1;
			// get the location of field delimiter characters
			while (match = fieldRegEx.exec(currentLine)) {
				// if the start position was located in the previous iteration, then this must be the end position
				if (startPos) {
					endPos = activeEditor.document.positionAt(positionOffset + match.index);
					var decoration = { range: new vscode.Range(startPos, endPos) };
					fieldSelectionListToReturn.push(decoration);
					startPos = null;
					endPos = null;
				}
				// if this field is in the list of fields to highlight, mark the start position in the message
				if (locationHashtable[fields[0].toUpperCase()].includes(fieldCount)) {
					startPos = activeEditor.document.positionAt(positionOffset + match.index + 1);
				}
				fieldCount++;
			}
			// check to see if the field requested was the last field in the segment (i.e. start of field delimiter found, but no further field delimiters).
			if (startPos) {
				endPos = activeEditor.document.positionAt(positionOffset + currentLine.length);
				var decoration = { range: new vscode.Range(startPos, endPos) };
				fieldSelectionListToReturn.push(decoration);
			}
		}
		// the field locations are relative to the current line, so calculate the offset of previous lines to identify the location within the file.
		positionOffset += currentLine.length + endOfLineLength;
	}
	return fieldSelectionListToReturn;
}

exports.ShowHighlights = ShowHighlights;
exports.HighlightFieldReturnCode = HighlightFieldReturnCode;<commit_msg>fixed Highligh Fields command due to change to config.files.eol values<commit_after>/*
    Rob Holme

    Functions to highlight user specified fields on a HL7 message.
*/

// load modules
const vscode = require('vscode');
const common = require('./common.js');

// the list of fields to highlight
var fieldSelectionList = [];

// return codes for ExtractAllFields()
const HighlightFieldReturnCode = {
	ERROR_NO_LOCATION_PROVIDED: 0,
	SUCCESS_NO_FIELD_FOUND: 1,
	SUCCESS_FIELD_FOUND: 2
};

//----------------------------------------------------
// Highlight the HL7 field(s) identified by the user
// @param {string} itemLocation - a string identifying the field location (e.g. PID-3)
// @param {object} hl7Schema - An object containing the HL7 schema
// @param {string} backgroundColor - The RGBA colour value identifying the background colour e.g. "rgba(0,255,0,0.3)". If this is not supplied, a default colour will be used.
//
// @returns {int} - returns the number of decorations applied
function ShowHighlights(itemLocation, hl7Schema, backgroundColor) {
	// the default background colour for highlighted items (if not otherwise specified)
	const defaultBackgroundColour = 'rgba(0,255,0,0.3)';

	// load the message delimiters from the current file
	var delimiters = common.ParseDelimiters();
	// regex to match a segment name and field index
	var itemLocationRegex = new RegExp("^[A-Z]{2}([A-Z]|[0-9])[-]([0-9]{1,3})$", 'i');
	// associative array indexed by segment name, with the field index as a value.
	var locationHashtable = {};
	var highlightBackgroundColour = defaultBackgroundColour;
	var activeEditor = vscode.window.activeTextEditor;

	// dispose of decorations for previously highlighted fields.
	if (fieldSelectionList.length > 0) {
		currentDecoration.dispose();
		fieldSelectionList = [];
	}

	// return if no location provided by the user, or no active editor
	if ((!itemLocation) || (!activeEditor)) {
		return HighlightFieldReturnCode.ERROR_NO_LOCATION_PROVIDED;
	}

	// if a background colour was provided use it, otherwise the default background setting defined in defaultBackgroundColour will apply
	if (backgroundColor) {
		highlightBackgroundColour = backgroundColor
	}

	// create a decorator type that is used to decorate selected fields
	var highlightDecorationType = vscode.window.createTextEditorDecorationType({
		backgroundColor: highlightBackgroundColour
	});

	// Test to see if the user has provided a valid location string, otherwise assume a description has been provided instead
	if (itemLocationRegex.test(itemLocation)) {
		// identify the segment name and field index from the location string
		var segmentName = common.GetSegmentNameFromLocationString(itemLocation).toUpperCase();
		var fieldIndex = parseInt(common.GetFieldIndex(itemLocation), 10);
		// the first field of MSH segments is the field delimiter, adjust index accordingly for MSH fields
		if (segmentName == "MSH") {
			fieldIndex--;
		}
		locationHashtable[segmentName] = [fieldIndex];
	}
	// else assume the user has provided a field description to search for instead of a location.
	else {
		locationHashtable = common.FindLocationFromDescription(itemLocation, hl7Schema);
	}

	fieldSelectionList = AddDecorations(activeEditor, locationHashtable, delimiters.FIELD)
	// warn the user if the field selected does not exist in the message
	if (fieldSelectionList.length < 1) {
//		vscode.window.showWarningMessage("A field matching " + itemLocation + " could not be located in the message");
		return HighlightFieldReturnCode.SUCCESS_NO_FIELD_FOUND;
	}

	// apply the decoration to highlight the field.
	activeEditor.setDecorations(highlightDecorationType, fieldSelectionList);
	currentDecoration = highlightDecorationType;
	return HighlightFieldReturnCode.SUCCESS_FIELD_FOUND;
}

//----------------------------------------------------
//  Locate the position of the fields to highlight, add the highlight decoration to a list
// @param {object} activeEditor - the current vscode editor
// @param {object} locationHashtable - An hashtable containing field locations to highlight. e.g. PID-3
// @param {char} fieldDelimiter - the field delimiter used by the HL7 message. e.g. '|'
//
// @return {object} - returns an array of the decoration objects (background highlighting) for each of the locations identified by the locationHashtable parameter.
function AddDecorations(activeEditor, locationHashtable, fieldDelimiter) {
	var positionOffset = 0;
	var fieldSelectionListToReturn = [];
	var fieldRegEx = new RegExp("\\" + fieldDelimiter, "g");
	// calculate the number of characters at the end of line (<CR>, or <CR><LF>)
	var currentDoc = activeEditor.document;
	endOfLineChar = common.GetEOLCharacter(currentDoc);
	var endOfLineLength = endOfLineChar.length;

	for (lineIndex = 0; lineIndex < activeEditor.document.lineCount; lineIndex++) {
		var startPos = null;
		var endPos = null;
		var currentLine = activeEditor.document.lineAt(lineIndex).text;
		var fields = currentLine.split(fieldDelimiter);
		if (!(locationHashtable[fields[0].toUpperCase()] === undefined)) {
			var fieldCount = 1;
			// get the location of field delimiter characters
			while (match = fieldRegEx.exec(currentLine)) {
				// if the start position was located in the previous iteration, then this must be the end position
				if (startPos) {
					endPos = activeEditor.document.positionAt(positionOffset + match.index);
					var decoration = { range: new vscode.Range(startPos, endPos) };
					fieldSelectionListToReturn.push(decoration);
					startPos = null;
					endPos = null;
				}
				// if this field is in the list of fields to highlight, mark the start position in the message
				if (locationHashtable[fields[0].toUpperCase()].includes(fieldCount)) {
					startPos = activeEditor.document.positionAt(positionOffset + match.index + 1);
				}
				fieldCount++;
			}
			// check to see if the field requested was the last field in the segment (i.e. start of field delimiter found, but no further field delimiters).
			if (startPos) {
				endPos = activeEditor.document.positionAt(positionOffset + currentLine.length);
				var decoration = { range: new vscode.Range(startPos, endPos) };
				fieldSelectionListToReturn.push(decoration);
			}
		}
		// the field locations are relative to the current line, so calculate the offset of previous lines to identify the location within the file.
		positionOffset += currentLine.length + endOfLineLength;
	}
	return fieldSelectionListToReturn;
}

exports.ShowHighlights = ShowHighlights;
exports.HighlightFieldReturnCode = HighlightFieldReturnCode;<|endoftext|>


<commit_before>import * as types from '../_constants/ActionTypes';
import { createTrade, destroyTrade } from '../_trade/saga/LifeCycleSaga';
import { getTradingOptions } from './TradingOptionsActions';
import { getTicksByCount } from './TickActions';
import { api } from '../_data/LiveData';

const getDailyPrices = symbol =>
    (dispatch, getState) => {
        const { dailyPrices } = getState();
        if (!dailyPrices.get(symbol)) {
            return api.getCandlesForLastNDays(symbol, 30);
        }
        return Promise.resolve();
    };

export const changeExaminedAsset = symbol =>
    dispatch => {
        const tradingOptions = dispatch(getTradingOptions(symbol));
        const dailyPrices = dispatch(getDailyPrices(symbol));
        const digitStats = dispatch(getTicksByCount(symbol, 100));
        Promise.all([tradingOptions, dailyPrices, digitStats])
            .then(() => dispatch({ type: types.CHANGE_INFO_FOR_ASSET, symbol }));
    };

export const changeActiveTab = (panel, index) => ({
    type: types.CHANGE_ACTIVE_TAB,
    panel,
    index,
});

export const updateActiveLayout = (tradesCount, layoutN, assetChoices) => ({
    type: types.CHANGE_ACTIVE_LAYOUT,
    tradesCount,
    layoutN,
    assetChoices,
});

export const changeActiveLayout = (tradesCount, layoutN) =>
    (dispatch, getState) => {
        const { assets, tradesParams } = getState();
        const currentTradeCount = tradesParams.size;
        const additionTradeNeeded = tradesCount - currentTradeCount;

        if (additionTradeNeeded < 0) {
            for (let i = 0; i < currentTradeCount - tradesCount; i++) {
                dispatch(destroyTrade(i + tradesCount));
            }
            return dispatch(updateActiveLayout(tradesCount, layoutN));
        }

        if (additionTradeNeeded === 0) {
            return dispatch(updateActiveLayout(tradesCount, layoutN));
        }

        const selectedSymbols = tradesParams.map(v => v.get('symbol'));
        const assetNotSync = assets.find(a => a.get('exchange_is_open') === 1) === undefined;
        if (assetNotSync) {
            return undefined;       // Active symbol call will create trade for us
        }
        const firstTradeSymbol = selectedSymbols.get(0) || assets.find(a => a.get('exchange_is_open') === 1).get('symbol');

        const firstTradeMarket = assets
            .find(v => v.get('symbol') === firstTradeSymbol)
            .get('market');
        const otherAssetInSameMarket = assets
            .filter(v => {
                const symbolNotSelected = !selectedSymbols.includes(v.get('symbol'));
                const sameMarketWithFirst = v.get('market') === firstTradeMarket;
                return symbolNotSelected && sameMarketWithFirst;
            })
            .sort((a, b) => b.get('exchange_is_open') - a.get('exchange_is_open'))
            .map(v => v.get('symbol'));

        if (otherAssetInSameMarket.size < additionTradeNeeded) {
            // get assets not found in otherAssetInSameMarket
            const randomAdditionalSymbols = assets
                .filter(v => otherAssetInSameMarket.find(a => a !== v.get('symbol')))
                .take(additionTradeNeeded - otherAssetInSameMarket.size);
            const combinedAssetChoices = otherAssetInSameMarket.concat(randomAdditionalSymbols);
            const assetToBeUsedForNewTrades = combinedAssetChoices.take(additionTradeNeeded);

            assetToBeUsedForNewTrades.forEach((asset, idx) => dispatch(createTrade(idx + currentTradeCount, asset)));
            return dispatch(updateActiveLayout(tradesCount, layoutN, assetToBeUsedForNewTrades.toJS()));
        }
        const assetToBeUsedForNewTrades = otherAssetInSameMarket.take(additionTradeNeeded);
        assetToBeUsedForNewTrades.forEach((asset, idx) => dispatch(createTrade(idx + currentTradeCount, asset)));
        return dispatch(updateActiveLayout(tradesCount, layoutN, assetToBeUsedForNewTrades.toJS()));
    };

export const changeActiveWorkspaceTab = (panel, index) => ({
    type: types.CHANGE_ACTIVE_WORKSPACE_TAB,
    panel,
    index,
});

export const changeActiveTrade = (activeTradeIndex) => ({
    type: types.CHANGE_ACTIVE_TRADE,
    activeTradeIndex,
});

export const changeWorkspacePanelSize = (panel, size) => ({
    type: types.CHANGE_WORKSPACE_PANEL_SIZE,
    panel,
    size,
});

export const changeTradeMode = tradeMode => ({
    type: types.CHANGE_TRADE_MODE,
    tradeMode,
});

export const toggleTradeMode = () => ({
    type: types.TOGGLE_TRADE_MODE,
});

export const togglePanel = panel => ({
    type: types.TOGGLE_PANEL,
    panel,
});
<commit_msg>Fix issue of closed market in weekeneds (#491)<commit_after>import * as types from '../_constants/ActionTypes';
import { createTrade, destroyTrade } from '../_trade/saga/LifeCycleSaga';
import { getTradingOptions } from './TradingOptionsActions';
import { getTicksByCount } from './TickActions';
import { api } from '../_data/LiveData';

const getDailyPrices = symbol =>
    (dispatch, getState) => {
        const { dailyPrices } = getState();
        if (!dailyPrices.get(symbol)) {
            return api.getCandlesForLastNDays(symbol, 30);
        }
        return Promise.resolve();
    };

export const changeExaminedAsset = symbol =>
    dispatch => {
        const tradingOptions = dispatch(getTradingOptions(symbol));
        const dailyPrices = dispatch(getDailyPrices(symbol));
        const digitStats = dispatch(getTicksByCount(symbol, 100));
        Promise.all([tradingOptions, dailyPrices, digitStats])
            .then(() => dispatch({ type: types.CHANGE_INFO_FOR_ASSET, symbol }));
    };

export const changeActiveTab = (panel, index) => ({
    type: types.CHANGE_ACTIVE_TAB,
    panel,
    index,
});

export const updateActiveLayout = (tradesCount, layoutN, assetChoices) => ({
    type: types.CHANGE_ACTIVE_LAYOUT,
    tradesCount,
    layoutN,
    assetChoices,
});

export const changeActiveLayout = (tradesCount, layoutN) =>
    (dispatch, getState) => {
        const { assets, tradesParams } = getState();
        const currentTradeCount = tradesParams.size;
        const additionTradeNeeded = tradesCount - currentTradeCount;

        if (additionTradeNeeded < 0) {
            for (let i = 0; i < currentTradeCount - tradesCount; i++) {
                dispatch(destroyTrade(i + tradesCount));
            }
            return dispatch(updateActiveLayout(tradesCount, layoutN));
        }

        if (additionTradeNeeded === 0) {
            return dispatch(updateActiveLayout(tradesCount, layoutN));
        }

        const selectedSymbols = tradesParams.map(v => v.get('symbol'));
        const assetNotSync = assets.find(a => a.get('exchange_is_open') === 1) === undefined;
        if (assetNotSync) {
            return undefined;       // Active symbol call will create trade for us
        }

        const firstOpenSymbol = assets.find(a => a.get('exchange_is_open') === 1);
        const firstTradeSymbol =
            selectedSymbols.get(0) ||
            (firstOpenSymbol ? firstOpenSymbol.get('symbol') : assets.getIn([0, 'symbol']));

        const firstTradeMarket = assets
            .find(v => v.get('symbol') === firstTradeSymbol)
            .get('market');
        const otherAssetInSameMarket = assets
            .filter(v => {
                const symbolNotSelected = !selectedSymbols.includes(v.get('symbol'));
                const sameMarketWithFirst = v.get('market') === firstTradeMarket;
                return symbolNotSelected && sameMarketWithFirst;
            })
            .sort((a, b) => b.get('exchange_is_open') - a.get('exchange_is_open'))
            .map(v => v.get('symbol'));

        if (otherAssetInSameMarket.size < additionTradeNeeded) {
            // get assets not found in otherAssetInSameMarket
            const randomAdditionalSymbols = assets
                .filter(v => otherAssetInSameMarket.find(a => a !== v.get('symbol')))
                .take(additionTradeNeeded - otherAssetInSameMarket.size);
            const combinedAssetChoices = otherAssetInSameMarket.concat(randomAdditionalSymbols);
            const assetToBeUsedForNewTrades = combinedAssetChoices.take(additionTradeNeeded);

            assetToBeUsedForNewTrades.forEach((asset, idx) => dispatch(createTrade(idx + currentTradeCount, asset)));
            return dispatch(updateActiveLayout(tradesCount, layoutN, assetToBeUsedForNewTrades.toJS()));
        }
        const assetToBeUsedForNewTrades = otherAssetInSameMarket.take(additionTradeNeeded);
        assetToBeUsedForNewTrades.forEach((asset, idx) => dispatch(createTrade(idx + currentTradeCount, asset)));
        return dispatch(updateActiveLayout(tradesCount, layoutN, assetToBeUsedForNewTrades.toJS()));
    };

export const changeActiveWorkspaceTab = (panel, index) => ({
    type: types.CHANGE_ACTIVE_WORKSPACE_TAB,
    panel,
    index,
});

export const changeActiveTrade = (activeTradeIndex) => ({
    type: types.CHANGE_ACTIVE_TRADE,
    activeTradeIndex,
});

export const changeWorkspacePanelSize = (panel, size) => ({
    type: types.CHANGE_WORKSPACE_PANEL_SIZE,
    panel,
    size,
});

export const changeTradeMode = tradeMode => ({
    type: types.CHANGE_TRADE_MODE,
    tradeMode,
});

export const toggleTradeMode = () => ({
    type: types.TOGGLE_TRADE_MODE,
});

export const togglePanel = panel => ({
    type: types.TOGGLE_PANEL,
    panel,
});
<|endoftext|>


<commit_before>
var async = require("async"),
  _ = require("underscore"),
  fs = require("fs"),
  crypto = require("crypto"),
  RestJS = require("restjs"),
  Promise = require("bluebird"),
  path = require("path");

  if(RestJS.Rest)
    RestJS = RestJS.Rest;

// Azuqua Node.js Client Library
// -----------------------------

// This library provides an easy interface for interacting with your Azuqua flos.
// The Azuqua API is directly exposed to developers should you wish to write your own library.
// For full API documentation please visit developer.azuqua.com.

// In order to make API requests you will need both your accessKey and accessSecret.
// These can be found on your account information page.

// Since using an Azuqua client is the most secure way to invoke a flo
// any published flo can be invoked with the credentials provided by this client, regardless of the flo's security level.

// All asynchronous functions can return a promise if you prefer that pattern.
// By default when you call an asynchronous function and
// leave the callback undefined it will return a promise.
// By default Azuqua uses bluebird.

// Should you wish to write your own library that makes HTTP requests directly you will need to provide the following headers with each request:

// `x-api-accessKey: "your access key",`

// `x-api-hash: "the hexidecimal string output of a HMAC-SHA256 of the data and some metadata signed by your accessSecret (see the signData function)",`

// `x-api-timestamp: "an ISO timestamp of the current time"`

// Also note that the API rate limits all clients by the maximum number of requests per second
// as determined by your account plan. By default this is 3 requests per second.

// Flos are not directly invocable via their name. To invoke a flo you fi
// rst need to
// get the alias for the flo. These can be found at the /api/account/flos route. This client maintains
// an internal mapping between each flo name and its alias so you can invoke flos directly by their name.
// Aliases are designed to be disposable so the flo maintainer can obfuscate endpoints as needed.

var routes = {
  invoke: {
    path: "/flo/:id/invoke",
    method: "POST"
  },
  inject: {
    path: "/flo/:id/inject",
    method: "POST"
  },
  flos: {
    path: "/account/flos",
    method: "GET"
  },
  schedule: {
    path: "/flo/:id/schedule",
    method: "POST"
  },
  retry: {
    path: "/flo/:id/retry",
    method: "POST"
  },
  inputs: {
    path: "/flo/:id/inputs",
    method: "GET"
  }
};

function deepDataCopy(target) {
  return JSON.parse(JSON.stringify(target));
}

var wrapAsyncFunction = function(fn, args){
  args = Array.prototype.slice.call(args);
  var callback = args[args.length - 1];
  if(typeof callback !== "function"){
    var deferred = Promise.defer();
    args.push(function(error, data){
      if(error)
        deferred.reject(error);
      else
        deferred.resolve(data);
    });
    fn.apply(fn, args);
    return deferred.promise;
  }else{
    fn.apply(fn, args);
  }
};

var signData = function(accessSecret, data, verb, _path, timestamp){
  if(!data)
    data = "";
  else if(typeof data === "object")
    data = JSON.stringify(data);
  var meta = [verb.toLowerCase(), _path, timestamp].join(":");
  return crypto.createHmac("sha256", accessSecret).update(new Buffer(meta + data, 'utf-8')).digest("hex");
};

var addGetParameter = function(_path, key, value){
  var delimiter = _path.indexOf("?") > -1 ? "&" : "?";
  return _path + delimiter + encodeURIComponent(key) + "=" + encodeURIComponent(value);
};

var getAlias = function(map, str){
  return map[str] || _.find(map, function(v){ return v === str; });
};

// Constructor
// -----------

// Create a new Azuqua client.

// This function first attempts to read accessKey and accessSecret data from environment variables.
// These can be set with ACCESS_KEY and ACCESS_SECRET.
// It then attempts to read them from the function's arguments.
// You can overwrite the environment variables this way if necessary,
// although it's not recommended to hard-code your account credentials.
var Azuqua = function(accessKey, accessSecret, httpOptions){
  var self = this;

  self.account = {};
  if(process.env.ACCESS_KEY)
    self.account.accessKey = process.env.ACCESS_KEY;
  if(process.env.ACCESS_SECRET)
    self.account.accessSecret = process.env.ACCESS_SECRET;
  if(typeof accessKey === "string")
    self.account.accessKey = accessKey;
  if(typeof accessSecret === "string")
    self.account.accessSecret = accessSecret;

  var protocol = "https";

  self.httpOptions = {
    host: "api.azuqua.com",
    port: 443,
    headers: {
      "Content-Type": "application/json"
    }
  };

  if(typeof httpOptions === "object"){
    if(httpOptions.protocol){
      protocol = httpOptions.protocol;
      delete httpOptions.protocol;
    }
    _.extend(self.httpOptions, httpOptions);
  }
  Object.freeze(self.httpOptions)
  self.client = new RestJS({ protocol: protocol });

  self.signData = function(data, verb, path, timestamp) {
    if(!self.account.accessSecret)
      throw new Error("Account information not found");

    return signData(self.account.accessSecret, data, verb, path, timestamp);
  };

  self.makeRequest = function(externalOptions, params, callback){
    if(!self.account || !self.account.accessKey || !self.account.accessSecret)
      return callback(new Error("Account information not found"));

    var options = deepDataCopy(externalOptions);

    _.each(self.httpOptions, function(value, key){
      if (typeof value === "object") {
        options[key] = deepDataCopy(value);
      } else {
        options[key] = value;
      }
    });

    var timestamp = new Date().toISOString();

    if(!params || Object.keys(params).length < 1) {
      params = options.method === 'GET' ? "" : {};
    }
    else {
      params = JSON.parse(JSON.stringify(params));
    }

    var hash = signData(self.account.accessSecret, params, options.method, options.path, timestamp);
    if(options.method === "GET"){
      _.each(params, function(key, value){
        options.path = addGetParameter(options.path, key, value);
      });
    }
    else{
      params = JSON.stringify(params);
    }

    if(options.method === "POST") {
      options.headers["Content-Length"] = Buffer.byteLength(params);
    }

    options.headers["x-api-timestamp"] = timestamp;
    options.headers["x-api-hash"] = hash;
    options.headers["x-api-accessKey"] = self.account.accessKey;
    self.client.request(options, params, function(error, resp){
      if(error){
        callback(error);
      }
      else{
        try{
          resp.body = JSON.parse(resp.body);
        }
        catch(e){
          return callback(resp.body);
        }

        if(resp.body.error) {
          callback(new Error(resp.body.error.message ? resp.body.error.message : resp.body.error));
        }
        else {
          callback(null, resp.body);
        }
      }
    });
  };

};

// Account Configuration
// ---------------------

// loadConfig

// Read accessKey and accessSecret data from a .json file.

// Note: This function blocks. Use loadConfigAsync for the async variant.
Azuqua.prototype.loadConfig = function(_path){
  // Resolve the config path from the requiring parent's location if it's relative
  _path = path.resolve(path.dirname(module.parent.filename), _path);
  this.account = require(_path);
};

// loadConfigAsync

// Asynchronously read accessKey and accessSecret data from a .json file.
Azuqua.prototype.loadConfigAsync = function(_path, _callback){
  var self = this,
      args = Array.prototype.slice.call(arguments);

  // Resolve the config path from the requiring parent's location if it's relative
  args[0] = path.resolve(path.dirname(module.parent.filename), _path);

  return wrapAsyncFunction(function(_path, callback){
    fs.readFile(_path, { encoding: "utf8" }, function(error, data){
      if(error){
        callback(error);
      }else{
        try{
          data = JSON.parse(data);
        }catch(e){
          return callback(e);
        }
        if(data.accessKey && data.accessSecret){
          self.account = data;
          callback(null, true);
        }else{
          callback(new Error("Invalid account credentials"));
        }
      }
    });
  }, args);
};

// API Functions
// -------------

// flos

// List all flos for your organization.

// Note: This caches the flos locally. To refresh them provide a truthy first parameter.
Azuqua.prototype.flos = function(_refresh, _callback){
  var self = this;
  return wrapAsyncFunction(function(refresh, callback){
    if(typeof refresh === "function"){
      callback = refresh;
      refresh = false;
    }
    if(self.floMap && !refresh){
      callback(null, Object.keys(self.floMap));
    }else{
      self.makeRequest(routes.flos, null, function(error, flos){
        if(error){
          callback(error);
        }else{
          self.floMap = {};
          var out = [];
          async.each(flos, function(flo, cb){
            if(!self.floMap[flo.name])
              out.push(flo.name);
            self.floMap[flo.name] = flo.alias;
            cb();
          }, function(){
            callback(null, out);
          });
        }
      });
    }
  }, arguments);
};

// Retry

// Retry a Flo identified by @instance_id
// @instance_id is a string representing a particula execution of a flo.
// @data is expected to be an object with the org and flo properties set to the
// original parent flo's id as well as the owner's org id.
Azuqua.prototype.retry = function(_flo, _data, _force, _callback){
  var self = this;
  return wrapAsyncFunction(function(flo, data, force, callback){
    if(typeof force === "function" && !callback){
      callback = force;
      force = false;
    }
    var options = _.extend({}, routes.retry);
    options.path = options.path.replace(":id", flo);
    self.makeRequest(options, data, callback);
  }, arguments);
};

// invoke

// Invoke a Flo identified by @flo with @data.
// @flo is a string representing the flo name.
// If you try to invoke a flo whose alias cannot be found the client will first attempt to refresh
// the flos cache once. Failing that it will call the callback or reject the promise with an error.
// If the optional parameter @force is true then it will attempt to invoke the flo without
// using the cache. Use this to avoid an extra network request if you already know the alias.
Azuqua.prototype.invoke = function(_flo, _data, _force, _callback){
  var self = this;
  return wrapAsyncFunction(function(flo, data, force, callback){
    if(typeof force === "function" && !callback){
      callback = force;
      force = false;
    }
    var alias = getAlias(self.floMap || {}, flo);
    if(!alias && force)
      alias = flo;
    if(alias){
      var options = _.extend({}, routes.invoke);
      options.path = options.path.replace(":id", alias);
      self.makeRequest(options, data, callback);
    }else{
      self.flos(true).then(function(){
        alias = getAlias(self.floMap, flo);
        if(alias)
          self.invoke(alias, data, callback);
        else
          callback(new Error("Flo not found"));
      }, callback);
    }
  }, arguments);
};

/**
 *
 * @param _flo - flo alias or flo name
 * @param _force - If true then it will attempt to retrieve the inputs of the flo without using the cache
 * @param _callback
 */
Azuqua.prototype.inputs = function (_flo, _force, _callback) {
  var self = this;
  return wrapAsyncFunction(function (flo, force, callback) {
    if (typeof force === "function" && !callback) {
      callback = force;
      force = false;
    }

    var alias = null;
    try {
      alias = getAlias(self.floMap || {}, flo);
    } catch (e) {
      return callback(e);
    }

    if (!alias && force) {
      alias = flo;
    }

    if (alias) {
      var options = _.extend({}, routes.inputs);
      options.path = options.path.replace(":id", alias);
      self.makeRequest(options, data, callback);
    }
    else {
      self.flos(true)
        .then(function () {
          alias = getAlias(self.floMap, flo);
          if (alias) {
            self.inputs(alias, force, callback);
          }
          else {
            callback(new Error("Flo not found"));
          }
        })
        .catch(callback);
    }
  }, arguments);
};

// inject

// Inject and run a Flo identified by @flo with @data mocking the first step.
// @flo is a string representing the flo name.
// If you try to inject a flo whose alias cannot be found the client will first attempt to refresh
// the flos cache once. Failing that it will call the callback or reject the promise with an error.
// If the optional parameter @force is true then it will attempt to inject the flo without
// using the cache. Use this to avoid an extra network request if you already know the alias.
Azuqua.prototype.inject = function(_flo, _data, _force, _callback){
  var self = this;
  return wrapAsyncFunction(function(flo, data, force, callback){
    if(typeof force === "function" && !callback){
      callback = force;
      force = false;
    }

    var alias = getAlias(self.floMap || {}, flo);
    if(!alias && force)
      alias = flo;
    if(alias){
      var options = _.extend({}, routes.inject);
      options.path = options.path.replace(":id", alias);
      self.makeRequest(options, data, callback);
    }else{
      self.flos(true).then(function(){
        alias = getAlias(self.floMap, flo);
        if(alias)
          self.inject(alias, data, callback);
        else
          callback(new Error("Flo not found"));
      }, callback);
    }
  }, arguments);
};

Azuqua.prototype.schedule = function(_flo, _data, _force, _callback) {
  var self = this;
  return wrapAsyncFunction(function(flo, data, force, callback){
    if(typeof force === "function" && !callback){
      callback = force;
      force = false;
    }

    var alias = getAlias(self.floMap || {}, flo);
    if(!alias && force)
      alias = flo;

    if(alias){
      var options = _.extend({}, routes.schedule);
      options.path = options.path.replace(":id", alias);
      self.makeRequest(options, data, callback);
    }
    else {
      self.flos(true).then(function(){
        alias = getAlias(self.floMap, flo);
        if(alias)
          self.schedule(alias, data, callback);
        else
          callback(new Error("Flo not found"));
      }, callback);
    }
  }, arguments);
};

module.exports = Azuqua;

<commit_msg>added inputs route<commit_after>
var async = require("async"),
  _ = require("underscore"),
  fs = require("fs"),
  crypto = require("crypto"),
  RestJS = require("restjs"),
  Promise = require("bluebird"),
  path = require("path");

  if(RestJS.Rest)
    RestJS = RestJS.Rest;

// Azuqua Node.js Client Library
// -----------------------------

// This library provides an easy interface for interacting with your Azuqua flos.
// The Azuqua API is directly exposed to developers should you wish to write your own library.
// For full API documentation please visit developer.azuqua.com.

// In order to make API requests you will need both your accessKey and accessSecret.
// These can be found on your account information page.

// Since using an Azuqua client is the most secure way to invoke a flo
// any published flo can be invoked with the credentials provided by this client, regardless of the flo's security level.

// All asynchronous functions can return a promise if you prefer that pattern.
// By default when you call an asynchronous function and
// leave the callback undefined it will return a promise.
// By default Azuqua uses bluebird.

// Should you wish to write your own library that makes HTTP requests directly you will need to provide the following headers with each request:

// `x-api-accessKey: "your access key",`

// `x-api-hash: "the hexidecimal string output of a HMAC-SHA256 of the data and some metadata signed by your accessSecret (see the signData function)",`

// `x-api-timestamp: "an ISO timestamp of the current time"`

// Also note that the API rate limits all clients by the maximum number of requests per second
// as determined by your account plan. By default this is 3 requests per second.

// Flos are not directly invocable via their name. To invoke a flo you fi
// rst need to
// get the alias for the flo. These can be found at the /api/account/flos route. This client maintains
// an internal mapping between each flo name and its alias so you can invoke flos directly by their name.
// Aliases are designed to be disposable so the flo maintainer can obfuscate endpoints as needed.

var routes = {
  invoke: {
    path: "/flo/:id/invoke",
    method: "POST"
  },
  inject: {
    path: "/flo/:id/inject",
    method: "POST"
  },
  flos: {
    path: "/account/flos",
    method: "GET"
  },
  schedule: {
    path: "/flo/:id/schedule",
    method: "POST"
  },
  retry: {
    path: "/flo/:id/retry",
    method: "POST"
  },
  inputs: {
    path: "/flo/:id/inputs",
    method: "GET"
  }
};

function deepDataCopy(target) {
  return JSON.parse(JSON.stringify(target));
}

var wrapAsyncFunction = function(fn, args){
  args = Array.prototype.slice.call(args);
  var callback = args[args.length - 1];
  if(typeof callback !== "function"){
    var deferred = Promise.defer();
    args.push(function(error, data){
      if(error)
        deferred.reject(error);
      else
        deferred.resolve(data);
    });
    fn.apply(fn, args);
    return deferred.promise;
  }else{
    fn.apply(fn, args);
  }
};

var signData = function(accessSecret, data, verb, _path, timestamp){
  if(!data)
    data = "";
  else if(typeof data === "object")
    data = JSON.stringify(data);
  var meta = [verb.toLowerCase(), _path, timestamp].join(":");
  return crypto.createHmac("sha256", accessSecret).update(new Buffer(meta + data, 'utf-8')).digest("hex");
};

var addGetParameter = function(_path, key, value){
  var delimiter = _path.indexOf("?") > -1 ? "&" : "?";
  return _path + delimiter + encodeURIComponent(key) + "=" + encodeURIComponent(value);
};

var getAlias = function(map, str){
  return map[str] || _.find(map, function(v){ return v === str; });
};

// Constructor
// -----------

// Create a new Azuqua client.

// This function first attempts to read accessKey and accessSecret data from environment variables.
// These can be set with ACCESS_KEY and ACCESS_SECRET.
// It then attempts to read them from the function's arguments.
// You can overwrite the environment variables this way if necessary,
// although it's not recommended to hard-code your account credentials.
var Azuqua = function(accessKey, accessSecret, httpOptions){
  var self = this;

  self.account = {};
  if(process.env.ACCESS_KEY)
    self.account.accessKey = process.env.ACCESS_KEY;
  if(process.env.ACCESS_SECRET)
    self.account.accessSecret = process.env.ACCESS_SECRET;
  if(typeof accessKey === "string")
    self.account.accessKey = accessKey;
  if(typeof accessSecret === "string")
    self.account.accessSecret = accessSecret;

  var protocol = "https";

  self.httpOptions = {
    host: "api.azuqua.com",
    port: 443,
    headers: {
      "Content-Type": "application/json"
    }
  };

  if(typeof httpOptions === "object"){
    if(httpOptions.protocol){
      protocol = httpOptions.protocol;
      delete httpOptions.protocol;
    }
    _.extend(self.httpOptions, httpOptions);
  }
  Object.freeze(self.httpOptions)
  self.client = new RestJS({ protocol: protocol });

  self.signData = function(data, verb, path, timestamp) {
    if(!self.account.accessSecret)
      throw new Error("Account information not found");

    return signData(self.account.accessSecret, data, verb, path, timestamp);
  };

  self.makeRequest = function(externalOptions, params, callback){
    if(!self.account || !self.account.accessKey || !self.account.accessSecret)
      return callback(new Error("Account information not found"));

    var options = deepDataCopy(externalOptions);

    _.each(self.httpOptions, function(value, key){
      if (typeof value === "object") {
        options[key] = deepDataCopy(value);
      } else {
        options[key] = value;
      }
    });

    var timestamp = new Date().toISOString();

    if(!params || Object.keys(params).length < 1) {
      params = options.method === 'GET' ? "" : {};
    }
    else {
      params = JSON.parse(JSON.stringify(params));
    }

    var hash = signData(self.account.accessSecret, params, options.method, options.path, timestamp);
    if(options.method === "GET"){
      _.each(params, function(key, value){
        options.path = addGetParameter(options.path, key, value);
      });
    }
    else{
      params = JSON.stringify(params);
    }

    if(options.method === "POST") {
      options.headers["Content-Length"] = Buffer.byteLength(params);
    }

    options.headers["x-api-timestamp"] = timestamp;
    options.headers["x-api-hash"] = hash;
    options.headers["x-api-accessKey"] = self.account.accessKey;
    self.client.request(options, params, function(error, resp){
      if(error){
        callback(error);
      }
      else{
        try{
          resp.body = JSON.parse(resp.body);
        }
        catch(e){
          return callback(resp.body);
        }

        if(resp.body.error) {
          callback(new Error(resp.body.error.message ? resp.body.error.message : resp.body.error));
        }
        else {
          callback(null, resp.body);
        }
      }
    });
  };

};

// Account Configuration
// ---------------------

// loadConfig

// Read accessKey and accessSecret data from a .json file.

// Note: This function blocks. Use loadConfigAsync for the async variant.
Azuqua.prototype.loadConfig = function(_path){
  // Resolve the config path from the requiring parent's location if it's relative
  _path = path.resolve(path.dirname(module.parent.filename), _path);
  this.account = require(_path);
};

// loadConfigAsync

// Asynchronously read accessKey and accessSecret data from a .json file.
Azuqua.prototype.loadConfigAsync = function(_path, _callback){
  var self = this,
      args = Array.prototype.slice.call(arguments);

  // Resolve the config path from the requiring parent's location if it's relative
  args[0] = path.resolve(path.dirname(module.parent.filename), _path);

  return wrapAsyncFunction(function(_path, callback){
    fs.readFile(_path, { encoding: "utf8" }, function(error, data){
      if(error){
        callback(error);
      }else{
        try{
          data = JSON.parse(data);
        }catch(e){
          return callback(e);
        }
        if(data.accessKey && data.accessSecret){
          self.account = data;
          callback(null, true);
        }else{
          callback(new Error("Invalid account credentials"));
        }
      }
    });
  }, args);
};

// API Functions
// -------------

// flos

// List all flos for your organization.

// Note: This caches the flos locally. To refresh them provide a truthy first parameter.
Azuqua.prototype.flos = function(_refresh, _callback){
  var self = this;
  return wrapAsyncFunction(function(refresh, callback){
    if(typeof refresh === "function"){
      callback = refresh;
      refresh = false;
    }
    if(self.floMap && !refresh){
      callback(null, Object.keys(self.floMap));
    }else{
      self.makeRequest(routes.flos, null, function(error, flos){
        if(error){
          callback(error);
        }else{
          self.floMap = {};
          var out = [];
          async.each(flos, function(flo, cb){
            if(!self.floMap[flo.name])
              out.push(flo.name);
            self.floMap[flo.name] = flo.alias;
            cb();
          }, function(){
            callback(null, out);
          });
        }
      });
    }
  }, arguments);
};

// Retry

// Retry a Flo identified by @instance_id
// @instance_id is a string representing a particula execution of a flo.
// @data is expected to be an object with the org and flo properties set to the
// original parent flo's id as well as the owner's org id.
Azuqua.prototype.retry = function(_flo, _data, _force, _callback){
  var self = this;
  return wrapAsyncFunction(function(flo, data, force, callback){
    if(typeof force === "function" && !callback){
      callback = force;
      force = false;
    }
    var options = _.extend({}, routes.retry);
    options.path = options.path.replace(":id", flo);
    self.makeRequest(options, data, callback);
  }, arguments);
};

// invoke

// Invoke a Flo identified by @flo with @data.
// @flo is a string representing the flo name.
// If you try to invoke a flo whose alias cannot be found the client will first attempt to refresh
// the flos cache once. Failing that it will call the callback or reject the promise with an error.
// If the optional parameter @force is true then it will attempt to invoke the flo without
// using the cache. Use this to avoid an extra network request if you already know the alias.
Azuqua.prototype.invoke = function(_flo, _data, _force, _callback){
  var self = this;
  return wrapAsyncFunction(function(flo, data, force, callback){
    if(typeof force === "function" && !callback){
      callback = force;
      force = false;
    }
    var alias = getAlias(self.floMap || {}, flo);
    if(!alias && force)
      alias = flo;
    if(alias){
      var options = _.extend({}, routes.invoke);
      options.path = options.path.replace(":id", alias);
      self.makeRequest(options, data, callback);
    }else{
      self.flos(true).then(function(){
        alias = getAlias(self.floMap, flo);
        if(alias)
          self.invoke(alias, data, callback);
        else
          callback(new Error("Flo not found"));
      }, callback);
    }
  }, arguments);
};

/**
 *
 * @param _flo - flo alias or flo name
 * @param _force - If true then it will attempt to retrieve the inputs of the flo without using the cache
 * @param _callback
 */
Azuqua.prototype.inputs = function (_flo, _force, _callback) {
  var self = this;
  return wrapAsyncFunction(function (flo, force, callback) {
    if (typeof force === "function" && !callback) {
      callback = force;
      force = false;
    }

    var alias = null;
    try {
      alias = getAlias(self.floMap || {}, flo);
    } catch (e) {
      return callback(e);
    }

    if (!alias && force) {
      alias = flo;
    }

    if (alias) {
      var options = _.extend({}, routes.inputs);
      options.path = options.path.replace(":id", alias);
      self.makeRequest(options, "", callback);
    }
    else {
      self.flos(true)
        .then(function () {
          alias = getAlias(self.floMap, flo);
          if (alias) {
            self.inputs(alias, force, callback);
          }
          else {
            callback(new Error("Flo not found"));
          }
        })
        .catch(callback);
    }
  }, arguments);
};

// inject

// Inject and run a Flo identified by @flo with @data mocking the first step.
// @flo is a string representing the flo name.
// If you try to inject a flo whose alias cannot be found the client will first attempt to refresh
// the flos cache once. Failing that it will call the callback or reject the promise with an error.
// If the optional parameter @force is true then it will attempt to inject the flo without
// using the cache. Use this to avoid an extra network request if you already know the alias.
Azuqua.prototype.inject = function(_flo, _data, _force, _callback){
  var self = this;
  return wrapAsyncFunction(function(flo, data, force, callback){
    if(typeof force === "function" && !callback){
      callback = force;
      force = false;
    }

    var alias = getAlias(self.floMap || {}, flo);
    if(!alias && force)
      alias = flo;
    if(alias){
      var options = _.extend({}, routes.inject);
      options.path = options.path.replace(":id", alias);
      self.makeRequest(options, data, callback);
    }else{
      self.flos(true).then(function(){
        alias = getAlias(self.floMap, flo);
        if(alias)
          self.inject(alias, data, callback);
        else
          callback(new Error("Flo not found"));
      }, callback);
    }
  }, arguments);
};

Azuqua.prototype.schedule = function(_flo, _data, _force, _callback) {
  var self = this;
  return wrapAsyncFunction(function(flo, data, force, callback){
    if(typeof force === "function" && !callback){
      callback = force;
      force = false;
    }

    var alias = getAlias(self.floMap || {}, flo);
    if(!alias && force)
      alias = flo;

    if(alias){
      var options = _.extend({}, routes.schedule);
      options.path = options.path.replace(":id", alias);
      self.makeRequest(options, data, callback);
    }
    else {
      self.flos(true).then(function(){
        alias = getAlias(self.floMap, flo);
        if(alias)
          self.schedule(alias, data, callback);
        else
          callback(new Error("Flo not found"));
      }, callback);
    }
  }, arguments);
};

module.exports = Azuqua;

<|endoftext|>


<commit_before>var Request = require('./Request');
var Response = require('./Response');
var merge = require('react/lib/merge');
var mergeInto = require('react/lib/mergeInto');
var addProxyMethods = require('./utils/addProxyMethods');

/**
 * A RoutingContext is an interface to a router that's bound to a specific
 * request. It helps to avoid easy mistakes when dealing with async operations.
 * For example, the following code is BAD:
 *
 *     Router.route('/path', function(ctx, done) {
 *       xhr('http://api.example.com', function() {
 *         ctx.router.setState(...); // subtle bug here.
 *       })
 *     });
 *
 * If the user makes a new request (clicks another link) while the XHR is in
 * progress, we shouldn't set the state in this handler. This could be fixed by
 * always checking `ctx.canceled()`:
 *
 *     Router.route('/path', function(ctx, done) {
 *       xhr('http://api.example.com', function() {
 *         if (!ctx.canceled()) {
 *           ctx.router.setState(...);
 *         }
 *       })
 *     });
 *
 * This is completely valid. However, it's easy to forget boilerplate. So the
 * RoutingContext exposes its own `setState` that handles the check for you:
 *
 *     Router.route('/path', function(ctx, done) {
 *       xhr('http://api.example.com', function() {
 *         ctx.setState(...);
 *       })
 *     });
 *
 * Ah…perfect!
 */
function RoutingContext(req, res) {
  this.request = req;
  this.response = res;
  mergeInto(this, req);
  addProxyMethods(this, res, ['setView', 'getViewProps', 'setViewProps',
    'renderView', 'unhandled']);
}

// Doing this is a little dangerous. If we ever want to mutate the Request
// object, we have to make sure we also mutate this one in the same way. But the
// Request really shouldn't be mutated so we shouldn't have a problem.
RoutingContext.prototype = merge(Request.prototype);

module.exports = RoutingContext;
<commit_msg>Well, actually…<commit_after>var Request = require('./Request');
var Response = require('./Response');
var merge = require('react/lib/merge');
var mergeInto = require('react/lib/mergeInto');
var addProxyMethods = require('./utils/addProxyMethods');

/**
 * A RoutingContext is an interface to a router that's bound to a specific
 * request. It helps to avoid easy mistakes when dealing with async operations.
 * For example, the following code is BAD:
 *
 *     Router.route('/path', function(ctx, done) {
 *       xhr('http://api.example.com', function() {
 *         ctx.router.setState(...); // subtle bug here.
 *       })
 *     });
 *
 * If the user makes a new request (clicks another link) while the XHR is in
 * progress, we shouldn't set the state in this handler. This could be fixed by
 * always checking `ctx.canceled()`:
 *
 *     Router.route('/path', function(ctx, done) {
 *       xhr('http://api.example.com', function() {
 *         if (!ctx.canceled()) {
 *           ctx.router.setState(...);
 *         }
 *       })
 *     });
 *
 * This is completely valid. However, it's easy to forget boilerplate. So the
 * RoutingContext exposes its own `setState` that handles the check for you:
 *
 *     Router.route('/path', function(ctx, done) {
 *       xhr('http://api.example.com', function() {
 *         ctx.setState(...);
 *       })
 *     });
 *
 * Ah…perfect!
 */
function RoutingContext(req, res) {
  this.request = req;
  this.response = res;
  mergeInto(this, req);
  addProxyMethods(this, res, ['setView', 'getViewProps', 'setViewProps',
    'renderView', 'unhandled']);
}

// Doing this is a little dangerous. If we ever want to mutate the Request
// object, we have to make sure we also mutate this one in the same way. But the
// Request really shouldn't be mutated (at least not after the RequestContext is
// created) so we shouldn't have a problem.
RoutingContext.prototype = merge(Request.prototype);

module.exports = RoutingContext;
<|endoftext|>


<commit_before>'use strict'

const express = require('express')
const bodyParser = require('body-parser')
const request = require('request')
const app = express()

const Wit = require('node-wit').Wit
const interactive = require('node-wit').interactive

// recommended to inject access tokens as environmental variables, e.g.
const token = process.env.PAGE_ACCESS_TOKEN
const witToken = process.env.WIT_TOKEN

// WIT INTEGRATION

const actions = {
  send(request, response) {
    const {sessionId, context, entities} = request
    const {text, quickreplies} = response
    return new Promise(function (resolve, reject) {
      console.log('sending...', JSON.stringify(response))
      return resolve()
    })
  },

  getForecast({context, entities}) {
    return new Promise(function (resolve, reject) {
      // Here should go the api call, e.g.:
      // context.forecast = apiCall(context.loc)
      context.forecast = 'sunny'
      return resolve(context)
    })
  }
}

const client = new Wit({witToken, actions})
interactive(client)

// BELOW HERE PROBABLY SHOULDNT BE TOUCHED FOR NOW.

app.set('port', (process.env.PORT || 5000))

// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({extended: false}))

// parse application/json
app.use(bodyParser.json())

// index
app.get('/', function (req, res) {
  res.send('hello world i am a secret bot')
})

// for facebook verification
app.get('/webhook/', function (req, res) {
  if (req.query['hub.verify_token'] === 'my_voice_is_my_password_verify_me') {
    res.send(req.query['hub.challenge'])
  }
  res.send('Error, wrong token')
})

// to post data
app.post('/webhook/', function (req, res) {
  let messaging_events = req.body.entry[0].messaging
  for (let i = 0; i < messaging_events.length; i++) {
    let event = req.body.entry[0].messaging[i]
    let sender = event.sender.id
    if (event.message && event.message.text && !event.message.is_echo) {
      let text = event.message.text
      if (text === 'Generic') {
        sendGenericMessage(sender)
        continue
      }
      sendTextMessage(sender, 'Text received, echo: ' + text.substring(0, 200))
    }
    if (event.postback) {
      let text = JSON.stringify(event.postback)
      sendTextMessage(sender, 'Postback received: ' + text.substring(0, 200), token)
      continue
    }
  }
  res.sendStatus(200)
})

function sendTextMessage (sender, text) {
  let messageData = { text: text }

  request({
    url: 'https://graph.facebook.com/v2.6/me/messages',
    qs: {access_token: token},
    method: 'POST',
    json: {
      recipient: {id: sender},
      message: messageData
    }
  }, function (error, response, body) {
    if (error) {
      console.log('Error sending messages: ', error)
    } else if (response.body.error) {
      console.log('Error: ', response.body.error)
    }
  })
}

function sendGenericMessage (sender) {
  let messageData = {
    'attachment': {
      'type': 'template',
      'payload': {
        'template_type': 'generic',
        'elements': [{
          'title': 'First card',
          'subtitle': 'Element #1 of an hscroll',
          'image_url': 'http://messengerdemo.parseapp.com/img/rift.png',
          'buttons': [{
            'type': 'web_url',
            'url': 'https://www.messenger.com',
            'title': 'web url'
          }, {
            'type': 'postback',
            'title': 'Postback',
            'payload': 'Payload for first element in a generic bubble'
          }]
        }, {
          'title': 'Second card',
          'subtitle': 'Element #2 of an hscroll',
          'image_url': 'http://messengerdemo.parseapp.com/img/gearvr.png',
          'buttons': [{
            'type': 'postback',
            'title': 'Postback',
            'payload': 'Payload for second element in a generic bubble'
          }]
        }]
      }
    }
  }
  request({
    url: 'https://graph.facebook.com/v2.6/me/messages',
    qs: {access_token: token},
    method: 'POST',
    json: {
      recipient: {id: sender},
      message: messageData
    }
  }, function (error, response, body) {
    if (error) {
      console.log('Error sending messages: ', error)
    } else if (response.body.error) {
      console.log('Error: ', response.body.error)
    }
  })
}

// spin spin sugar
app.listen(app.get('port'), function () {
  console.log('running on port', app.get('port'))
})
<commit_msg>Not needed<commit_after><|endoftext|>


<commit_before>'use strict';
import marked from "marked";
import Base from './base.js';
import toc from 'markdown-toc';
import highlight from 'highlight.js';
import push2Firekylin from 'push-to-firekylin';

export default class extends Base {
  modelInstance = this.modelInstance.where({type: 0});
  /**
   * get
   * @return {[type]} [description]
   */
  async getAction(self){
    // this.modelInstance.field('id,user_id,type,status,title,pathname,create_time,update_time');
    let data;
    if( this.id ) {
      if( this.id === 'lastest' ) {
        return this.lastest();
      }
      data = await this.modelInstance.where({id: this.id}).find();
      //文章选项
      if(data.options){
        data.options = JSON.parse(data.options) || {};
      }else{
        data.options = {};
      }
    } else {
      let where = {};
      //不是管理员，只显示个人的文章
      if(this.userInfo.type !== 1){
        where.user_id = this.userInfo.id;
      }

      if(this.get('status')) {
        where.status = this.get('status');
      }

      if(this.get('keyword')) {
        let keywords = this.get('keyword').split(/\s+/g);
        if( keywords.indexOf(':public') > -1 || keywords.indexOf(':private') > -1 ) {
          where.is_public = Number(keywords.indexOf(':public') > -1);
          keywords = keywords.filter(word => word !== ':public' && word !== ':private');
        }
        if(keywords.length > 0) {
          where.title = ["like", keywords.map(word => `%${word}%`)];
        }
      }

      let field = ['id', 'title', 'user_id', 'create_time', 'update_time', 'status', 'pathname', 'is_public'];
      data = await this.modelInstance.where(where).field(field).order('create_time DESC').page( this.get('page'), 15 ).countSelect();
    }
    return this.success(data);
  }

  getBaseAction(self) {
    return super.getAction(self);
  }
  /**
   * add user
   * @return {[type]} [description]
   */
  async postAction(){
    let data = this.post();
    //check pathname
    let post = await this.modelInstance.where({pathname: data.pathname}).select();
    if( post.length > 0 ) {
      return this.fail('PATHNAME_EXIST');
    }

    /** 如果是编辑发布文章的话默认状态改为审核中 **/
    if( data.status == 3 && this.userInfo.type != 1 ) {
      data.status = 1;
    }

    /** 推送文章 **/
    this.pushPost(data);

    data.tag = await this.getTagIds(data.tag);
    data = await this.getContentAndSummary(data);
    data.user_id = this.userInfo.id;
    data = this.getPostTime(data);
    data.options = data.options ? JSON.stringify(data.options) : '';

    let insertId = await this.modelInstance.addPost(data);
    return this.success({id: insertId});
  }
  /**
   * update user info
   * @return {[type]} [description]
   */
  async putAction(){
    if (!this.id) {
      return this.fail('PARAMS_ERROR');
    }

    let data = this.post();
    data.id = this.id;

    /** 判断接收的参数中是否有 markdown_content 来区别审核通过的状态修改和普通的文章更新 */
    if( data.markdown_content ) {
      /** 推送文章 */
      this.pushPost(data);

      data = this.getPostTime(data);
      data = await this.getContentAndSummary(data);
      data.options = data.options ? JSON.stringify(data.options) : '';
      if(data.tag) {
        data.tag = await this.getTagIds(data.tag);
      }
    }

    let rows = await this.modelInstance.savePost(data);
    return this.success({affectedRows: rows});
  }

  async deleteAction() {
    if(!this.id) {
      return this.fail('PARAMS_ERROR');
    }

    /** 如果不是管理员且不是本文作者则无权限删除文章 **/
    if(this.userInfo.type !== 1) {
      let post = await this.modelInstance.where({id: this.id}).find();
      if( post.user_id != this.userInfo.id ) {
        return this.fail('USER_NO_PERMISSION');
      }
    }

    await this.modelInstance.deletePost(this.id);
    return this.success();
  }

  async pushPost(post) {
    let postOpt = JSON.parse(post.options);
    let canPush = Array.isArray(postOpt.push_sites) && postOpt.push_sites.length > 0;
    if( post.status != 3 && post.is_public != 1 && !canPush ) {
      return;
    }
    post = think.extend({}, post);

    let options = await this.model('options').getOptions();
    let push_sites = options.push_sites;
    let push_sites_keys = postOpt.push_sites;

    if( post.markdown_content.slice(0, 5) !== '> 原文：') {
      let site_url = options.hasOwnProperty('site_url') ? options.site_url : `http://${this.http.host}`;
      post.markdown_content = `> 原文：${site_url}/post/${post.pathname}.html

${post.markdown_content}`;
    }
    delete post.cate;
    delete post.options;

    if(!Array.isArray(push_sites_keys)) { push_sites_keys = [push_sites_keys]; }
    let pushes = push_sites_keys.map(key => {
      let {appKey, appSecret, url} = push_sites[key];
      let p2fk = new push2Firekylin(url, appKey, appSecret);
      return p2fk.push(post);
    });
    let result = await Promise.all(pushes);
    console.log('push result for debug: ', result);
  }

  async lastest() {
    let userId = this.userInfo.type !== 1 ? this.userInfo.id : null;
    let data = await this.modelInstance.getLatest(userId, 6);
    return this.success(data);
  }

  getPostTime(data) {
    data.update_time = think.datetime();
    if( !data.create_time ) {
      data.create_time = data.update_time;
    }else{
      data.create_time = think.datetime(data.create_time);
    }
    return data;
  }

  /**
   * 渲染 markdown
   * 摘要为部分内容时不展示 TOC
   * 文章正文设置为手动指定 TOC 时不显示
   * 页面不自动生成 TOC 除非是手动指定了
   */
  async getContentAndSummary(data) {
    let options = await this.model('options').getOptions();
    let postTocManual = options.postTocManual === '1';

    let showToc;
    if( !postTocManual ) {
      showToc = data.type/1 === 0;
    } else {
      showToc = /(?:^|[\r\n]+)\s*\<\!--toc--\>\s*[\r\n]+/i.test(data.markdown_content);
    }

    data.content = this.markdownToHtml(data.markdown_content, {toc: showToc, highlight: true});
    data.summary = data.markdown_content.split('')[0];
    data.summary = this.markdownToHtml(data.summary, {toc: false, highlight: true});
    data.summary.replace(/<[>]*>/g, '');

    return data;
  }

  async getTagIds(tags) {
    if(!tags){
      return [];
    }
    if(!think.isArray(tags)){
      tags = [tags];
    }
    let modelInstance = this.model('tag').setRelation(false), tagIds = [];
    let promises = tags.map(name =>
      modelInstance.where({name}).thenAdd({name, pathname: encodeURIComponent(name)}).then(data => tagIds.push({tag_id: data.id, name: name}))
    );
    await Promise.all(promises);
    return tagIds;
  }

  /**
   * generate toc name
   * @param  {String} name []
   * @return {String}      []
   */
  generateTocName(name){
    name = name.trim().replace(/\s+/g, '').replace(/\)/g, '').replace(/[\(\,]/g, '-').toLowerCase();
    if(/^[\w\-]+$/.test(name)){
      return name;
    }
    return `toc-${think.md5(name).slice(0, 3)}`;
  }
  /**
   * markdown to html
   * @return {} []
   */
  markdownToHtml(content, option = {toc: true, highlight: true}){
    let markedContent = marked(content);

    /**
     * 增加 TOC 目录
     */
    if( option.toc ) {
      let tocContent = marked(toc(content).content).replace(/([^<>]+)<\/a>/g, (a, b, c) => {
        return `<a href="#${this.generateTocName(c)}">${c}</a>`;
      });

      markedContent = markedContent.replace(/<h(\d)[^<>]*>(.*?)<\/h\1>/g, (a, b, c) => {
        return `<h${b}><a id="${this.generateTocName(c)}" class="anchor" href="#${this.generateTocName(c)}"></a>${c}</h${b}>`;
      });
      markedContent = `<div class="toc">${tocContent}</div>${markedContent}`;
    }

    /**
     * 增加代码高亮
     */
    if( option.highlight ) {
      markedContent = markedContent.replace(/
([\s\S]+?)<\/code><\/pre>/mg, (a, language, text) => {
        text = text.replace(/'/g, '\'').replace(/>/g, '>').replace(/</g, '<').replace(/\"/g, '"').replace(/\&/g, "&");
        var result = highlight.highlightAuto(text, language ? [language] : undefined);
        return `<pre><code class="hljs lang-${result.language}">${result.value}</code></pre>`;
      });
    }

    return markedContent;
  }

}
<commit_msg>fix editor update post will pass audit bug<commit_after>'use strict';
import marked from "marked";
import Base from './base.js';
import toc from 'markdown-toc';
import highlight from 'highlight.js';
import push2Firekylin from 'push-to-firekylin';

export default class extends Base {
  modelInstance = this.modelInstance.where({type: 0});
  /**
   * get
   * @return {[type]} [description]
   */
  async getAction(self){
    // this.modelInstance.field('id,user_id,type,status,title,pathname,create_time,update_time');
    let data;
    if( this.id ) {
      if( this.id === 'lastest' ) {
        return this.lastest();
      }
      data = await this.modelInstance.where({id: this.id}).find();
      //文章选项
      if(data.options){
        data.options = JSON.parse(data.options) || {};
      }else{
        data.options = {};
      }
    } else {
      let where = {};
      //不是管理员，只显示个人的文章
      if(this.userInfo.type !== 1){
        where.user_id = this.userInfo.id;
      }

      if(this.get('status')) {
        where.status = this.get('status');
      }

      if(this.get('keyword')) {
        let keywords = this.get('keyword').split(/\s+/g);
        if( keywords.indexOf(':public') > -1 || keywords.indexOf(':private') > -1 ) {
          where.is_public = Number(keywords.indexOf(':public') > -1);
          keywords = keywords.filter(word => word !== ':public' && word !== ':private');
        }
        if(keywords.length > 0) {
          where.title = ["like", keywords.map(word => `%${word}%`)];
        }
      }

      let field = ['id', 'title', 'user_id', 'create_time', 'update_time', 'status', 'pathname', 'is_public'];
      data = await this.modelInstance.where(where).field(field).order('create_time DESC').page( this.get('page'), 15 ).countSelect();
    }
    return this.success(data);
  }

  getBaseAction(self) {
    return super.getAction(self);
  }
  /**
   * add user
   * @return {[type]} [description]
   */
  async postAction(){
    let data = this.post();
    //check pathname
    let post = await this.modelInstance.where({pathname: data.pathname}).select();
    if( post.length > 0 ) {
      return this.fail('PATHNAME_EXIST');
    }

    /** 如果是编辑发布文章的话默认状态改为审核中 **/
    if( data.status == 3 && this.userInfo.type != 1 ) {
      data.status = 1;
    }

    /** 推送文章 **/
    this.pushPost(data);

    data.tag = await this.getTagIds(data.tag);
    data = await this.getContentAndSummary(data);
    data.user_id = this.userInfo.id;
    data = this.getPostTime(data);
    data.options = data.options ? JSON.stringify(data.options) : '';

    let insertId = await this.modelInstance.addPost(data);
    return this.success({id: insertId});
  }
  /**
   * update user info
   * @return {[type]} [description]
   */
  async putAction(){
    if (!this.id) {
      return this.fail('PARAMS_ERROR');
    }

    let data = this.post();
    data.id = this.id;

    /** 判断接收的参数中是否有 markdown_content 来区别审核通过的状态修改和普通的文章更新 */
    if( data.markdown_content ) {
      /** 如果是编辑发布文章的话默认状态改为审核中 **/
      if( data.status == 3 && this.userInfo.type != 1 ) {
        data.status = 1;
      }

      /** 推送文章 */
      this.pushPost(data);

      data = this.getPostTime(data);
      data = await this.getContentAndSummary(data);
      data.options = data.options ? JSON.stringify(data.options) : '';
      if(data.tag) {
        data.tag = await this.getTagIds(data.tag);
      }
    }

    let rows = await this.modelInstance.savePost(data);
    return this.success({affectedRows: rows});
  }

  async deleteAction() {
    if(!this.id) {
      return this.fail('PARAMS_ERROR');
    }

    /** 如果不是管理员且不是本文作者则无权限删除文章 **/
    if(this.userInfo.type !== 1) {
      let post = await this.modelInstance.where({id: this.id}).find();
      if( post.user_id != this.userInfo.id ) {
        return this.fail('USER_NO_PERMISSION');
      }
    }

    await this.modelInstance.deletePost(this.id);
    return this.success();
  }

  async pushPost(post) {
    let postOpt = JSON.parse(post.options);
    let canPush = Array.isArray(postOpt.push_sites) && postOpt.push_sites.length > 0;
    if( post.status != 3 && post.is_public != 1 && !canPush ) {
      return;
    }
    post = think.extend({}, post);

    let options = await this.model('options').getOptions();
    let push_sites = options.push_sites;
    let push_sites_keys = postOpt.push_sites;

    if( post.markdown_content.slice(0, 5) !== '> 原文：') {
      let site_url = options.hasOwnProperty('site_url') ? options.site_url : `http://${this.http.host}`;
      post.markdown_content = `> 原文：${site_url}/post/${post.pathname}.html

${post.markdown_content}`;
    }
    delete post.cate;
    delete post.options;

    if(!Array.isArray(push_sites_keys)) { push_sites_keys = [push_sites_keys]; }
    let pushes = push_sites_keys.map(key => {
      let {appKey, appSecret, url} = push_sites[key];
      let p2fk = new push2Firekylin(url, appKey, appSecret);
      return p2fk.push(post);
    });
    let result = await Promise.all(pushes);
    console.log('push result for debug: ', result);
  }

  async lastest() {
    let userId = this.userInfo.type !== 1 ? this.userInfo.id : null;
    let data = await this.modelInstance.getLatest(userId, 6);
    return this.success(data);
  }

  getPostTime(data) {
    data.update_time = think.datetime();
    if( !data.create_time ) {
      data.create_time = data.update_time;
    }else{
      data.create_time = think.datetime(data.create_time);
    }
    return data;
  }

  /**
   * 渲染 markdown
   * 摘要为部分内容时不展示 TOC
   * 文章正文设置为手动指定 TOC 时不显示
   * 页面不自动生成 TOC 除非是手动指定了
   */
  async getContentAndSummary(data) {
    let options = await this.model('options').getOptions();
    let postTocManual = options.postTocManual === '1';

    let showToc;
    if( !postTocManual ) {
      showToc = data.type/1 === 0;
    } else {
      showToc = /(?:^|[\r\n]+)\s*\<\!--toc--\>\s*[\r\n]+/i.test(data.markdown_content);
    }

    data.content = this.markdownToHtml(data.markdown_content, {toc: showToc, highlight: true});
    data.summary = data.markdown_content.split('')[0];
    data.summary = this.markdownToHtml(data.summary, {toc: false, highlight: true});
    data.summary.replace(/<[>]*>/g, '');

    return data;
  }

  async getTagIds(tags) {
    if(!tags){
      return [];
    }
    if(!think.isArray(tags)){
      tags = [tags];
    }
    let modelInstance = this.model('tag').setRelation(false), tagIds = [];
    let promises = tags.map(name =>
      modelInstance.where({name}).thenAdd({name, pathname: encodeURIComponent(name)}).then(data => tagIds.push({tag_id: data.id, name: name}))
    );
    await Promise.all(promises);
    return tagIds;
  }

  /**
   * generate toc name
   * @param  {String} name []
   * @return {String}      []
   */
  generateTocName(name){
    name = name.trim().replace(/\s+/g, '').replace(/\)/g, '').replace(/[\(\,]/g, '-').toLowerCase();
    if(/^[\w\-]+$/.test(name)){
      return name;
    }
    return `toc-${think.md5(name).slice(0, 3)}`;
  }
  /**
   * markdown to html
   * @return {} []
   */
  markdownToHtml(content, option = {toc: true, highlight: true}){
    let markedContent = marked(content);

    /**
     * 增加 TOC 目录
     */
    if( option.toc ) {
      let tocContent = marked(toc(content).content).replace(/([^<>]+)<\/a>/g, (a, b, c) => {
        return `<a href="#${this.generateTocName(c)}">${c}</a>`;
      });

      markedContent = markedContent.replace(/<h(\d)[^<>]*>(.*?)<\/h\1>/g, (a, b, c) => {
        return `<h${b}><a id="${this.generateTocName(c)}" class="anchor" href="#${this.generateTocName(c)}"></a>${c}</h${b}>`;
      });
      markedContent = `<div class="toc">${tocContent}</div>${markedContent}`;
    }

    /**
     * 增加代码高亮
     */
    if( option.highlight ) {
      markedContent = markedContent.replace(/
([\s\S]+?)<\/code><\/pre>/mg, (a, language, text) => {
        text = text.replace(/'/g, '\'').replace(/>/g, '>').replace(/</g, '<').replace(/\"/g, '"').replace(/\&/g, "&");
        var result = highlight.highlightAuto(text, language ? [language] : undefined);
        return `<pre><code class="hljs lang-${result.language}">${result.value}</code></pre>`;
      });
    }

    return markedContent;
  }

}
<|endoftext|>


<commit_before>var leafletDirective = angular.module("leaflet-directive", []);

leafletDirective.directive("leaflet", ["$http", "$log", function ($http, $log) {

    var defaults = {
        maxZoom: 14,
        tileLayer: 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
        icon: {
            url: 'http://cdn.leafletjs.com/leaflet-0.5.1/images/marker-icon.png',
            retinaUrl: 'http://cdn.leafletjs.com/leaflet-0.5.1/images/marker-icon@2x.png',
            size: [25, 41],
            anchor: [12, 40],
            popup: [0, -40],
            shadow: {
                url: 'http://cdn.leafletjs.com/leaflet-0.5.1/images/marker-shadow.png',
                retinaUrl: 'http://cdn.leafletjs.com/leaflet-0.5.1/images/marker-shadow.png',
                size: [41, 41],
                anchor: [12, 40]
            }
        },
        path: {
            weight: 10,
            opacity: 1
        }
    };

    return {
        restrict: "E",
        replace: true,
        transclude: true,
        scope: {
            center: '=center',
            markers: '=markers',
            defaults: '=defaults',
            path: '=path'
        },
        template: '

',
        link: function ($scope, element, attrs /*, ctrl */) {
            var map = new L.Map(element[0]);
            map.setView([0, 0], 1);

            $scope.leaflet = {};
            $scope.leaflet.map = !!attrs.testing ? map : 'Add testing="testing" to <leaflet> tag to inspect this object';
            $scope.leaflet.maxZoom = !!(attrs.defaults && $scope.defaults.maxZoom) ? parseInt($scope.defaults.maxZoom, 10) : defaults.maxZoom;
            $scope.leaflet.tileLayer = !!(attrs.defaults && $scope.defaults.tileLayer) ? $scope.defaults.tileLayer : defaults.tileLayer;
            L.tileLayer($scope.leaflet.tileLayer, { maxZoom: $scope.leaflet.maxZoom }).addTo(map);

            setupCenter();
            setupMarkers();
            setupPath();

            function setupCenter() {
                if (!$scope.center) {
                    return;
                }

                if ($scope.center.lat && $scope.center.lng && $scope.center.zoom) {
                    map.setView([$scope.center.lat, $scope.center.lng], $scope.center.zoom);
                } else if ($scope.center.autoDiscover === true) {
                    map.locate({ setView: true, maxZoom: $scope.leaflet.maxZoom });
                }

                map.on("dragend", function (/* event */) {
                    $scope.$apply(function (scope) {
                        scope.center.lat = map.getCenter().lat;
                        scope.center.lng = map.getCenter().lng;
                    });
                });

                map.on("zoomend", function (/* event */) {
                    if ($scope.center.zoom !== map.getZoom()) {
                        $scope.$apply(function (s) {
                            s.center.zoom = map.getZoom();
                        });
                    }
                });

                $scope.$watch("center", function (center /*, oldValue */) {
                    if (center.lat && center.lng && center.zoom) {
                        map.setView([center.lat, center.lng], center.zoom);
                    }
                }, true);
            }

            function setupMarkers() {
                var markers = {};
                $scope.leaflet.markers = !!attrs.testing ? markers : 'Add testing="testing" to <leaflet> tag to inspect this object';

                if (!$scope.markers) {
                    return;
                }

                for (var name in $scope.markers) {
                    markers[name] = createMarker(name, $scope.markers[name], map);
                }

                $scope.$watch("markers", function (newMarkers /*, oldMarkers*/) {
                    for (var name in newMarkers) {
                        if (markers[name] === undefined) {
                            markers[name] = createMarker(name, newMarkers[name], map);
                        }
                    }

                    // Delete markers from the array
                    for (var name in markers) {
                        if (newMarkers[name] === undefined) {
                            delete markers[name];
                        }
                    }

                }, true);
            }

            function createMarker(name, scopeMarker, map) {
                var marker = buildMarker(name, scopeMarker);

                marker.on("dragend", function () {
                    $scope.$apply(function (scope) {
                        scopeMarker.lat = marker.getLatLng().lat;
                        scopeMarker.lng = marker.getLatLng().lng;
                    });
                    if (scopeMarker.message) {
                        marker.openPopup();
                    }
                });

                $scope.$watch('markers.' + name, function (data, oldData) {
                    if (!data) {
                        map.removeLayer(marker);
                        return;
                    }

                    if (oldData) {
                        if (data.draggable !== undefined && data.draggable !== oldData.draggable) {
                            if (data.draggable === true) {
                                marker.dragging.enable();
                            } else {
                                marker.dragging.disable();
                            }
                        }

                        if (data.focus !== undefined && data.focus !== oldData.focus) {
                            if (data.focus === true) {
                                marker.openPopup();
                            } else {
                                marker.closePopup();
                            }
                        }

                        if (data.message !== undefined && data.message !== oldData.message) {
                            marker.bindPopup(data);
                        }

                        if (data.lat !== oldData.lat || data.lng !== oldData.lng) {
                            marker.setLatLng(new L.LatLng(data.lat, data.lng));
                        }
                    }
                }, true);
                map.addLayer(marker);
                return marker;
            }

            function buildMarker(name, data) {
                var marker = new L.marker($scope.markers[name],
                        {
                            icon: buildIcon(),
                            draggable: data.draggable ? true : false
                        }
                );
                if (data.message) {
                    marker.bindPopup(data.message);
                }
                return marker;
            }

            function buildIcon() {
                return L.icon({
                    iconUrl: defaults.icon.url,
                    iconRetinaUrl: defaults.icon.retinaUrl,
                    iconSize: defaults.icon.size,
                    iconAnchor: defaults.icon.anchor,
                    popupAnchor: defaults.icon.popup,
                    shadowUrl: defaults.icon.shadow.url,
                    shadowRetinaUrl: defaults.icon.shadow.retinaUrl,
                    shadowSize: defaults.icon.shadow.size,
                    shadowAnchor: defaults.icon.shadow.anchor
                });
            }

            function setupPath() {
                // TODO Create as many polylines as paths defined in model
                // TODO Manage opacity changes with another $watch block
                if (!$scope.path) {
                    return;
                }

                $log.warn("[AngularJS - Leaflet] Creating polylines and adding them to the map will break the directive's scope's inspection in AngularJS Batarang");

                var polyline = new L.Polyline([], { weight: defaults.path.weight, opacity: defaults.path.opacity});
                $scope.leaflet.path = !!attrs.testing ? polyline : 'Add testing="testing" to <leaflet> tag to inspect this object';

                map.addLayer(polyline);

                $scope.$watch("path.latlngs", function (latlngs) {
                    var leafletLatLngs = latlngs
                            .filter(function (latlng) {
                                return !!latlng.lat && !!latlng.lng;
                            })
                            .map(function (latlng) {
                                return new L.LatLng(latlng.lat, latlng.lng);
                            });
                    polyline.setLatLngs(leafletLatLngs);
                }, true);

                $scope.$watch("path.weight", function (weight) {
                    polyline.setStyle({ weight: weight });
                }, true);

                $scope.$watch("path.color", function (color) {
                    polyline.setStyle({ color: color });
                }, true);
            }
        }
    };
}]);
<commit_msg>pass jshint again<commit_after>var leafletDirective = angular.module("leaflet-directive", []);

leafletDirective.directive("leaflet", ["$http", "$log", function ($http, $log) {

    var defaults = {
        maxZoom: 14,
        tileLayer: 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
        icon: {
            url: 'http://cdn.leafletjs.com/leaflet-0.5.1/images/marker-icon.png',
            retinaUrl: 'http://cdn.leafletjs.com/leaflet-0.5.1/images/marker-icon@2x.png',
            size: [25, 41],
            anchor: [12, 40],
            popup: [0, -40],
            shadow: {
                url: 'http://cdn.leafletjs.com/leaflet-0.5.1/images/marker-shadow.png',
                retinaUrl: 'http://cdn.leafletjs.com/leaflet-0.5.1/images/marker-shadow.png',
                size: [41, 41],
                anchor: [12, 40]
            }
        },
        path: {
            weight: 10,
            opacity: 1
        }
    };

    return {
        restrict: "E",
        replace: true,
        transclude: true,
        scope: {
            center: '=center',
            markers: '=markers',
            defaults: '=defaults',
            path: '=path'
        },
        template: '

',
        link: function ($scope, element, attrs /*, ctrl */) {
            var map = new L.Map(element[0]);
            map.setView([0, 0], 1);

            $scope.leaflet = {};
            $scope.leaflet.map = !!attrs.testing ? map : 'Add testing="testing" to <leaflet> tag to inspect this object';
            $scope.leaflet.maxZoom = !!(attrs.defaults && $scope.defaults.maxZoom) ? parseInt($scope.defaults.maxZoom, 10) : defaults.maxZoom;
            $scope.leaflet.tileLayer = !!(attrs.defaults && $scope.defaults.tileLayer) ? $scope.defaults.tileLayer : defaults.tileLayer;
            L.tileLayer($scope.leaflet.tileLayer, { maxZoom: $scope.leaflet.maxZoom }).addTo(map);

            setupCenter();
            setupMarkers();
            setupPath();

            function setupCenter() {
                if (!$scope.center) {
                    return;
                }

                if ($scope.center.lat && $scope.center.lng && $scope.center.zoom) {
                    map.setView([$scope.center.lat, $scope.center.lng], $scope.center.zoom);
                } else if ($scope.center.autoDiscover === true) {
                    map.locate({ setView: true, maxZoom: $scope.leaflet.maxZoom });
                }

                map.on("dragend", function (/* event */) {
                    $scope.$apply(function (scope) {
                        scope.center.lat = map.getCenter().lat;
                        scope.center.lng = map.getCenter().lng;
                    });
                });

                map.on("zoomend", function (/* event */) {
                    if ($scope.center.zoom !== map.getZoom()) {
                        $scope.$apply(function (s) {
                            s.center.zoom = map.getZoom();
                        });
                    }
                });

                $scope.$watch("center", function (center /*, oldValue */) {
                    if (center.lat && center.lng && center.zoom) {
                        map.setView([center.lat, center.lng], center.zoom);
                    }
                }, true);
            }

            function setupMarkers() {
                var markers = {};
                $scope.leaflet.markers = !!attrs.testing ? markers : 'Add testing="testing" to <leaflet> tag to inspect this object';

                if (!$scope.markers) {
                    return;
                }

                for (var name in $scope.markers) {
                    markers[name] = createMarker(name, $scope.markers[name], map);
                }

                $scope.$watch("markers", function (newMarkers /*, oldMarkers*/) {
                    for (var new_name in newMarkers) {
                        if (markers[new_name] === undefined) {
                            markers[new_name] = createMarker(new_name, newMarkers[new_name], map);
                        }
                    }

                    // Delete markers from the array
                    for (var name in markers) {
                        if (newMarkers[name] === undefined) {
                            delete markers[name];
                        }
                    }

                }, true);
            }

            function createMarker(name, scopeMarker, map) {
                var marker = buildMarker(name, scopeMarker);

                marker.on("dragend", function () {
                    $scope.$apply(function (scope) {
                        scopeMarker.lat = marker.getLatLng().lat;
                        scopeMarker.lng = marker.getLatLng().lng;
                    });
                    if (scopeMarker.message) {
                        marker.openPopup();
                    }
                });

                $scope.$watch('markers.' + name, function (data, oldData) {
                    if (!data) {
                        map.removeLayer(marker);
                        return;
                    }

                    if (oldData) {
                        if (data.draggable !== undefined && data.draggable !== oldData.draggable) {
                            if (data.draggable === true) {
                                marker.dragging.enable();
                            } else {
                                marker.dragging.disable();
                            }
                        }

                        if (data.focus !== undefined && data.focus !== oldData.focus) {
                            if (data.focus === true) {
                                marker.openPopup();
                            } else {
                                marker.closePopup();
                            }
                        }

                        if (data.message !== undefined && data.message !== oldData.message) {
                            marker.bindPopup(data);
                        }

                        if (data.lat !== oldData.lat || data.lng !== oldData.lng) {
                            marker.setLatLng(new L.LatLng(data.lat, data.lng));
                        }
                    }
                }, true);
                map.addLayer(marker);
                return marker;
            }

            function buildMarker(name, data) {
                var marker = new L.marker($scope.markers[name],
                        {
                            icon: buildIcon(),
                            draggable: data.draggable ? true : false
                        }
                );
                if (data.message) {
                    marker.bindPopup(data.message);
                }
                return marker;
            }

            function buildIcon() {
                return L.icon({
                    iconUrl: defaults.icon.url,
                    iconRetinaUrl: defaults.icon.retinaUrl,
                    iconSize: defaults.icon.size,
                    iconAnchor: defaults.icon.anchor,
                    popupAnchor: defaults.icon.popup,
                    shadowUrl: defaults.icon.shadow.url,
                    shadowRetinaUrl: defaults.icon.shadow.retinaUrl,
                    shadowSize: defaults.icon.shadow.size,
                    shadowAnchor: defaults.icon.shadow.anchor
                });
            }

            function setupPath() {
                // TODO Create as many polylines as paths defined in model
                // TODO Manage opacity changes with another $watch block
                if (!$scope.path) {
                    return;
                }

                $log.warn("[AngularJS - Leaflet] Creating polylines and adding them to the map will break the directive's scope's inspection in AngularJS Batarang");

                var polyline = new L.Polyline([], { weight: defaults.path.weight, opacity: defaults.path.opacity});
                $scope.leaflet.path = !!attrs.testing ? polyline : 'Add testing="testing" to <leaflet> tag to inspect this object';

                map.addLayer(polyline);

                $scope.$watch("path.latlngs", function (latlngs) {
                    var leafletLatLngs = latlngs
                            .filter(function (latlng) {
                                return !!latlng.lat && !!latlng.lng;
                            })
                            .map(function (latlng) {
                                return new L.LatLng(latlng.lat, latlng.lng);
                            });
                    polyline.setLatLngs(leafletLatLngs);
                }, true);

                $scope.$watch("path.weight", function (weight) {
                    polyline.setStyle({ weight: weight });
                }, true);

                $scope.$watch("path.color", function (color) {
                    polyline.setStyle({ color: color });
                }, true);
            }
        }
    };
}]);
<|endoftext|>